Full Code of elecV2/elecV2P-dei for AI

master 6b4673acd0c0 cached
91 files
316.1 KB
120.2k tokens
111 symbols
1 requests
Download .txt
Showing preview only (428K chars total). Download the full file or copy to clipboard to get everything.
Repository: elecV2/elecV2P-dei
Branch: master
Commit: 6b4673acd0c0
Files: 91
Total size: 316.1 KB

Directory structure:
gitextract_iq6s9hbq/

├── .gitignore
├── Readme.md
├── docs/
│   ├── 01-overview.md
│   ├── 02-Docker.md
│   ├── 03-rules.md
│   ├── 04-JS.md
│   ├── 05-rewrite.md
│   ├── 06-task.md
│   ├── 07-feed&notify.md
│   ├── 08-logger&efss.md
│   ├── 09-webhook.md
│   ├── 10-config.md
│   ├── Advanced.md
│   ├── Readme.md
│   ├── dev_note/
│   │   ├── archive/
│   │   │   ├── favend JS 重构-efh.md
│   │   │   ├── minishell subprocess.md
│   │   │   ├── webUI.md
│   │   │   └── websocket 通信协议设计.md
│   │   ├── clash delegate efh.md
│   │   ├── elecV2P 错误自检指南.md
│   │   ├── ev 命令行程序.md
│   │   ├── favend JS 重构-efh.md
│   │   ├── favend 模块化.md
│   │   ├── readme.md
│   │   ├── runJSFile 执行逻辑及优化.md
│   │   ├── script_store.efh 应用中心.md
│   │   ├── service workers 开发与优化.md
│   │   ├── sse 通信模块.md
│   │   ├── store 常量加密存储读取.md
│   │   ├── webUI transparent mode.md
│   │   ├── webUI 主题设计.md
│   │   ├── webUI 首页快捷运行程序 eapp.md
│   │   ├── webhook token 权限设计.md
│   │   ├── 下载其他扩展程序.md
│   │   ├── 关于引入区块链的可行性.md
│   │   ├── 可能永不执行的长期计划.md
│   │   ├── 启动器快捷方式 $run.md
│   │   ├── 开发者激励计划.md
│   │   ├── 引入广告系统.md
│   │   ├── 待深度优化部分.md
│   │   ├── 根据 mitmhost 生成 pac 文件.md
│   │   ├── 脚本缓存_内容结果等.md
│   │   ├── 节点互联.md
│   │   └── 通过脚本管理规则 $rewrite.md
│   └── res/
│       └── logo/
│           └── readme.md
├── examples/
│   ├── JS-elecV2P.sublime-build
│   ├── JSTEST/
│   │   ├── 0body.js
│   │   ├── TGbotonFavend.js
│   │   ├── aria2-env.js
│   │   ├── asyncPool.js
│   │   ├── boxjs.ev.js
│   │   ├── cheerio-hbin.js
│   │   ├── efh/
│   │   │   ├── kuwo-music.efh
│   │   │   ├── markdown.efh
│   │   │   ├── notepad.efh
│   │   │   └── readme.md
│   │   ├── evui-chatroom.js
│   │   ├── evui-dou.js
│   │   ├── exam-ahk-send.js
│   │   ├── exam-ahk.js
│   │   ├── exam-chcp.js
│   │   ├── exam-clipboard.js
│   │   ├── exam-rss.js
│   │   ├── exam-tasksub.js
│   │   ├── example-cheerio.js
│   │   ├── example-rule.js
│   │   ├── fendtest.efh
│   │   ├── github-subdownload.js
│   │   ├── markdown.efh
│   │   ├── reboot.js
│   │   ├── simple.efh
│   │   ├── starturl.js
│   │   ├── tgbotmessage.js
│   │   └── webonlinetest.js
│   ├── Readme.md
│   ├── Shell/
│   │   ├── aria2c
│   │   ├── elecV2P-runjs.ahk
│   │   ├── exam-request.py
│   │   ├── mousemove.ahk
│   │   └── sendkey.ahk
│   ├── TGbotonCFworker.js
│   ├── TGbotonCFworker2.0.js
│   ├── archive/
│   │   └── Caddyfile
│   ├── docker-compose-clash.yaml
│   ├── docker-compose.yaml
│   ├── ev2p-nginx.conf
│   └── theme/
│       ├── elecV2P_theme.20220420.json
│       └── readme.md
└── information/
    ├── readme.md
    ├── 广告位招租.md
    └── 开发者激励计划.md

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

================================================
FILE: .gitignore
================================================
.git

================================================
FILE: Readme.md
================================================
## elecV2P 文档/例程/通知/反馈 - documents/examples/information/issues

主项目地址: https://github.com/elecV2/elecV2P

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/overview.png)

TG 频道: https://t.me/elecV2
TG 交流群: https://t.me/elecV2G

欢迎提交功能需求或者其他建议。

### 说明

- 文档目录:[docs](https://github.com/elecV2/elecV2P-dei/tree/master/docs)
- 例程目录:[examples](https://github.com/elecV2/elecV2P-dei/tree/master/examples)
- 初次使用建议先把 docs 内容浏览一遍,涉及内容较多,大部分可以直接跳过,在使用中碰到问题时再来查看

================================================
FILE: docs/01-overview.md
================================================
```
最近更新: 2022-10-21
适用版本: 3.7.3
```

*此章节内容同步自 [elecV2P Readme 文档](https://github.com/elecV2/elecV2P)*

## 简介

elecV2P - customize personal network.
一款基于 NodeJS,可通过 JS 修改网络请求,以及定时运行脚本或 SHELL 指令的网络工具。

![elecV2P overview/预览](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/overview.png)

### 基础功能

- 查看/修改网络请求 (MITM)
- 定时执行 JS/SHELL 脚本
- FEED/IFTTT/自定义 通知
- EFSS 基础文件管理

## 安装/INSTALL

***程序开放权限极大,建议局域网使用。公网部署(务必参考 [Advanced.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/Advanced.md)),风险自负***

*elecV2P 所有文件及依赖总大小约 90 M。初始运行时内存占用约 90 M,运行 100 个定时任务时总内存占用约 150 M(仅供参考,不同软硬件条件下程序调用资源可能有所不同)*

**在可使用 Docker 的情况下,推荐使用方法三进行安装**

### 方法一:直接 NODEJS 运行

**需求 NODEJS 版本 (node -v) >= 14.17.0**

``` sh
git clone https://github.com/elecV2/elecV2P.git
cd elecV2P

# 安装依赖库(根据网络环境和硬盘读写速度,需要 1-10 分钟不等
yarn

# elecV2P 默认以 pm2 的方式启动,需要先安装好 pm2
# pm2 的安装方式:
# 1. 添加 elecV2P 所在目录/node_modules/.bin 到系统环境变量 PATH 中
# 2. 或者直接执行 yarn global add pm2
# 然后执行命令
yarn start

# 其他基础方式启动命令
node index.js
# 假如提示 80 端口不可用,尝试命令
# windows 平台 CMD:
# set PORT=8000 && node index.js
# windows 平台 PowerShell:
# $env:PORT="8000";node index.js
# 其他平台:
# PORT=8000 TZ=Asia/Shanghai node index.js
## TZ=Asia/Shanghai 用于设置程序运行时区
```

#### 升级

方式一:使用 [softupdate.js](https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/softupdate.js) 软更新升级

- 首先在 webUI/JSMANAGE 脚本管理中找到 softupdate.js 文件,假如不存在就远程推送或本地上传一下
- 然后按照文件内的说明,根据自身需求更改 CONFIG 设置项
- 最后点击测试运行即可

方式二:手动升级(不推荐

- 先备份好个人数据,比如 根证书,以及 efss、script/JSFile、Store、Lists、Shell 等文件夹
- (推荐在 webUI/efss 界面,右键对应文件夹,然后 zip 打包下载。)
- 然后在项目目录下执行命令 git pull,拉取最新的代码进行覆盖升级
- 最后再把备份好的文件上传/复制还原到之前的位置

### 其他 PM2 相关指令

``` sh
pm2 stop elecV2P  # 停止 elecV2P
pm2 stop all      # 停止所有程序

pm2 restart elecV2P   # 重启 elecV2P
pm2 restart 0

pm2 ls      # 查看运行状态
pm2 logs    # 查看运行日志

pm2 -h      # 查看 PM2 帮助列表
```

### 方法二:DOCKER

镜像名称: elecv2/elecv2p
镜像地址: https://hub.docker.com/r/elecv2/elecv2p

``` sh
# 基础使用命令
docker run --restart=always -d --name elecv2p -e TZ=Asia/Shanghai -p 80:80 -p 8001:8001 -p 8002:8002 elecv2/elecv2p

# 推荐使用命令
docker run --restart=always \
  -d --name elecv2p \
  -e TZ=Asia/Shanghai \
  -p 8100:80 -p 8101:8001 -p 8102:8002 \
  -v /elecv2p/JSFile:/usr/local/app/script/JSFile \
  -v /elecv2p/Lists:/usr/local/app/script/Lists \
  -v /elecv2p/Store:/usr/local/app/script/Store \
  -v /elecv2p/Shell:/usr/local/app/script/Shell \
  -v /elecv2p/rootCA:/usr/local/app/rootCA \
  -v /elecv2p/efss:/usr/local/app/efss \
  elecv2/elecv2p

# -p/-v 对应参数 宿主:容器
# 如需更改默认的 80 端口,可在 -e 后面加上 PORT=8000
# 升级 Docker 镜像(如果没有使用 -v 持久化存储,容器内数据会丢失,请提前备份)
docker rm -f elecv2p           # 先删除旧的容器
docker pull elecv2/elecv2p     # 再拉取新的镜像
# 再使用之前的 docker run xxxx 命令重新启动一下
# 如果拉取到的镜像不是最新的版本,请修改 Docker 当前使用的仓库地址
```

- ARM32 平台如果出错,参考 [issues #78](https://github.com/elecV2/elecV2P/issues/78)

### 方法三:DOCKER-COMPOSE (推荐)

``` sh
# 创建 elecV2P 持久化数据保存目录
mkdir /elecv2p && cd /elecv2p
# 假如失败,请尝试在其他有权限的目录进行创建
# 后面 docker-compose.yaml 映射目录保持和创建的目录一致

# 下载 docker-compose.yaml 文件
curl -sL https://git.io/JLw7s > docker-compose.yaml
# 启动运行 elecV2P
docker-compose up -d

# 注意: 需提前安装好 docker-compose 管理器
# 默认将 80/8001/8002 端口分别映射到了宿主机的 8100/8101/8102 端口,以防出现占用的情况
# 如果需要设置为其他端口,请自行修改 docker-compose.yaml 文件内容,然后重新启动
```

以下为 docker-compose.yaml 文件内容,可根据自身需求进行修改。

``` yaml
version: '3.7'
services:
  elecv2p:
    image: elecv2/elecv2p
    container_name: elecv2p
    restart: always
    environment:
      - TZ=Asia/Shanghai
    ports:
      - "8100:80"
      - "8101:8001"
      - "8102:8002"
    volumes:
      - "/elecv2p/JSFile:/usr/local/app/script/JSFile"
      - "/elecv2p/Lists:/usr/local/app/script/Lists"
      - "/elecv2p/Store:/usr/local/app/script/Store"
      - "/elecv2p/Shell:/usr/local/app/script/Shell"
      - "/elecv2p/rootCA:/usr/local/app/rootCA"
      - "/elecv2p/efss:/usr/local/app/efss"
```

修改后保存文件,然后在 docker-compose.yaml 文件所在目录下执行以下任一命令

``` sh
# 直接启动(首次启动命令)
docker-compose up -d

# 更新镜像并重新启动
docker-compose pull elecv2p && docker-compose up -d
```

- 如果在某些设备上无法启动,尝试把文件开头的 version: '3.7' 更改为 version: '3.3'
- ARM32 平台如果出错,参考 [issues #78](https://github.com/elecV2/elecV2P/issues/78)

其他 docker 相关指令

``` sh
# 查看是否启动及对应端口
docker ps

# 查看 elecV2P 运行日志
docker logs elecv2p -f
```

## 默认端口

- 80:    webUI 后台管理界面。用于添加规则/管理脚本/定时任务/MITM 证书 等
- 8001:  ANYPROXY HTTP 代理端口。(*代理端口不是网页,不能通过浏览器直接访问*)
- 8002:  ANYPROXY 代理请求查看端口

**ANYPROXY 相关端口默认关闭。可在 webUI 首页双击 ANYPROXY 临时开启。**
**如需在启动时自动开启,请前往 webUI->SETTING->初始化相关设置 中进行设置。**
**80/8002 对应端口需要用到 websocket,在使用 nginx 等反代工具时注意设置。参考 [ev2p-nginx.conf](https://github.com/elecV2/elecV2P-dei/blob/master/examples/ev2p-nginx.conf)**

- *80 端口可使用环境变量 **PORT** 进行修改(比如: PORT=8000 node index.js)*
- *在 elecV2P 已经启动时,可在 webUI->SETTING->初始化相关设置 中修改其他端口*
- *在 elecV2P 尚未启动时,可在 script/Lists/config.json 文件中修改对应端口*

## 根证书相关 - HTTPS 解密

- *如果不使用 RULES/REWRITE 等 MITM 相关功能,此步骤可跳过。*
- *升级启动后,如果不是使用之前的证书,需要重新下载安装信任根证书。*
- *根证书包含两个文件 rootCA.crt/rootCA.key,文件名不可修改。*

### 安装证书

选择以下任意一种方式下载证书,然后安装并信任

- 直接打开 :80/crt
- :80 -> MITM -> 安装证书
- :8002 -> RootCA

根证书物理存储目录位于 `$HOME/.anyproxy/certificates`。

*windows 平台的证书存储位置选择 浏览->受信任的根证书颁发机构*

### 使用自签根证书

在 webUI->MITM 界面上传自签根证书,然后重启 elecV2P

**注意:使用新的证书后,记得重新下载安装信任证书,并清除由之前根证书签发的域名证书。**

## RULES - 网络请求修改

![rules](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/rules.png)

详细说明参考: [docs/03-rules.md](https://github.com/elecV2/elecV2P-dei/tree/master/docs/03-rules.md)

## 定时任务

![task](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/taskall.png)

支持两种定时方式:

- 倒计时
- cron 定时

### 时间格式:

- 倒计时 30 999 3 2  (以空格分开的四个数字,后三项可省略)

|    30(秒)    |     999(次)   |      3(秒)         |       2(次)       
:--------------: | :-------------: | :------------------: | :------------------:
| 基础倒计时时间 | 重复次数(可选)| 增加随机时间(可选) | 增加随机重复次数(可选)  


- *当重复次数大于等于 **999** 时,无限循环*

示例: 40 8 10 3 ,表示倒计时40秒,随机10秒,所以具体倒计时时间位于 40-50 秒之间,重复运行 8-11 次

- cron 定时 

时间格式:* * * * * * (五/六位 cron 时间格式)

| * (0-59)   |  * (0-59)  |  * (0-23)  |  * (1-31)  |  * (1-12)  |  * (0-7)      
:----------: | :--------: | :--------: | :--------: | :--------: | :---------:
| 秒(可选) |    分      |    小时    |     日     |     月     |    星期


### 可执行任务类型

- 运行 JS
- 开始/停止 其他定时任务
- 基础 shell 指令。比如 *rm -f \**, *python test.py*, *reboot* 等等

更多说明参考:[docs/06-task.md](https://github.com/elecV2/elecV2P-dei/tree/master/docs/06-task.md)

## 通知

目前支持通知方式:
- FEED/RSS 订阅
- IFTTT WEBHOOK
- BARK 通知
- 自定义通知

FEED/RSS 订阅地址为 webUI/feed。

通知内容:
- 定时任务开始/结束
- 定时任务 JS 运行次数
- 脚本中的自主调用通知

IFTTT/BARK/自定义通知等相关设置参考: [07-feed&notify](https://github.com/elecV2/elecV2P-dei/tree/master/docs/07-feed&notify.md)

## DOCUMENTS&EXAMPLES

说明文档及一些例程: [https://github.com/elecV2/elecV2P-dei](https://github.com/elecV2/elecV2P-dei)

如果遇到问题欢迎 [open a issue](https://github.com/elecV2/elecV2P/issues)。尽量说明使用平台,版本,以及附上相关的错误日志(提供的信息越详细,越有助于解决问题)。

TG 频道: https://t.me/elecV2
TG 交流群: https://t.me/elecV2G

## 更新日志

查看: https://github.com/elecV2/elecV2P/blob/master/logs/update.log

## 贡献参考

- [anyproxy](https://github.com/alibaba/anyproxy)
- [axios](https://github.com/axios/axios)
- [expressjs](https://expressjs.com)
- [node-cron](https://github.com/merencia/node-cron)
- [node-rss](https://github.com/dylang/node-rss)
- [pm2](https://pm2.keymetrics.io)
- [vue](https://vuejs.org)
- [vue-draggable-resizable](https://github.com/mauricius/vue-draggable-resizable)
- [ace](https://github.com/ajaxorg/ace)
- [adm-zip](https://github.com/cthackers/adm-zip)
- [Ant Design Vue](https://www.antdv.com)

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/02-Docker.md
================================================
```
最近更新: 2022-03-15
适用版本: 3.6.3
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/02-Docker.md
```

## 简介

Docker 镜像名称: elecv2/elecv2p
Docker 镜像地址: https://hub.docker.com/r/elecv2/elecv2p

## docker 及 docker-compose 的安装

``` sh
# 不同平台的安装方式可能不一样,仅供参考
# docker 安装
wget -qO- https://get.docker.com/ | sh

# docker-compose 安装。(前往 https://github.com/docker/compose/releases 查看适合自己设备的版本)
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```

## Docker 运行 elecV2P

*以下命令仅供参考,具体映射端口和卷根据实际情况进行调整*

```sh
# 基础启动命令(重建后数据会丢失)
docker run --restart=always -d --name elecv2p -e TZ=Asia/Shanghai -p 80:80 -p 8001:8001 -p 8002:8002 elecv2/elecv2p

# 推荐使用命令
docker run --restart=always \
  -d --name elecv2p \
  -e TZ=Asia/Shanghai \
  -p 8100:80 -p 8101:8001 -p 8102:8002 \
  -v /elecv2p/JSFile:/usr/local/app/script/JSFile \
  -v /elecv2p/Lists:/usr/local/app/script/Lists \
  -v /elecv2p/Store:/usr/local/app/script/Store \
  -v /elecv2p/Shell:/usr/local/app/script/Shell \
  -v /elecv2p/rootCA:/usr/local/app/rootCA \
  -v /elecv2p/efss:/usr/local/app/efss \
  elecv2/elecv2p

# -p/-v 对应环境参数 宿主参数:容器内参数
# 宿主机映射目录尽量填写尚未创建或空的文件夹
# 如需更改默认的 80 端口,可在 -e 后面加上 PORT=8000
# 某些设备上,可能无法在根目录创建 elecv2p 文件夹,这时请根据使用设备搜索可操作的目录,进行替换
# 如果在部分复杂的网络情况下出现无法联网或访问的问题,尝试在命令中添加 --net=host

# 查看 docker 运行状态
docker ps

# 进入容器内部
docker exec -it elecv2p /bin/sh

# Docker 的启动暂停
docker start elecv2p
docker stop elecv2p
docker restart elecv2p

# 查看 Docker 运行日志
docker logs elecv2p -f
docker logs elecv2p --tail 20

# 清除 Docker 运行日志
echo "" > $(docker inspect --format='{{.LogPath}}' elecv2p)

# 升级容器
# 先移除容器
docker rm -f elecv2p
# 再拉取最新的镜像
docker pull elecv2/elecv2p
# 最后再使用上面的 docker run 命令重新启动
```

## docker-compose 启动

``` sh
mkdir /elecv2p && cd /elecv2p
curl -sL https://git.io/JLw7s > docker-compose.yaml

docker-compose up -d

# 默认把 80/8001/8002 端口分别映射成了 8100/8101/8102,以防出现端口占用的情况,访问时注意
# 如果需要设置为其他端口,可以自行修改下面的内容然后手动保存
```

或者将以下内容手动保存为 docker-compose.yaml 文件。

``` yaml
version: '3.7'
services:
  elecv2p:
    image: elecv2/elecv2p
    container_name: elecv2p
    restart: always
    environment:
      - TZ=Asia/Shanghai
    ports:
      - "8100:80"
      - "8101:8001"
      - "8102:8002"
    volumes:
      - "/elecv2p/JSFile:/usr/local/app/script/JSFile"
      - "/elecv2p/Lists:/usr/local/app/script/Lists"
      - "/elecv2p/Store:/usr/local/app/script/Store"
      - "/elecv2p/Shell:/usr/local/app/script/Shell"
      - "/elecv2p/rootCA:/usr/local/app/rootCA"
      - "/elecv2p/efss:/usr/local/app/efss"
```

- *具体使用的映射端口和 volumes 目录,根据个人情况进行调整*
- *如需更改默认的 80 端口,在 environment 下添加一行: - PORT=8000*
- *如果在某些设备上无法启动,尝试把文件开头的 version: '3.7' 更改为 version: '3.3'*

然后在 docker-compose.yaml 同目录执行命令 **docker-compose up -d** ,启动程序。

### env 默认环境变量

在 elecV2P 启动前,可设置部分环境变量

- TZ: 时区设置 timezone
- PORT: webUI 对应端口,默认为 80
- TOKEN: 启动时指定 WEBHOOK TOKEN

使用示例:

``` sh
docker run --restart=always \
  -d --name elecv2p \
  -e TZ=Asia/Shanghai PORT=8000 TOKEN=YOUR-WEBHOOK-TOKEN \
  -p 8100:8000 -p 8101:8001 -p 8102:8002 \
  -v /elecv2p/JSFile:/usr/local/app/script/JSFile \
  -v /elecv2p/Lists:/usr/local/app/script/Lists \
  -v /elecv2p/Store:/usr/local/app/script/Store \
  -v /elecv2p/Shell:/usr/local/app/script/Shell \
  -v /elecv2p/rootCA:/usr/local/app/rootCA \
  -v /elecv2p/efss:/usr/local/app/efss \
  elecv2/elecv2p
```

环境变量可以同时设置部分或全部

**在 docker-compose 中 env 对应 environment**

### 其他指令

``` sh
# 更新升级
docker-compose pull elecv2p && docker-compose up -d

# 拉取特定版本的镜像文件。可用版本以 https://hub.docker.com/r/elecv2/elecv2p 的 tag 为准
docker pull elecv2/elecv2p:3.4.5
docker pull elecv2/elecv2p:arm64-3.0    # 在使用这些特定版本的镜像时,docker run 后面的镜像名也要记得调整

docker image prune       # 清除没有挂载的镜像文件

# 查看运行日志
docker logs elecv2p -f
```

### 一些说明

- 当使用国内的一些 docker 源,因为缓存问题,更新之后可能不是最新的版本,需要手动更换一下 docker 源。(具体步骤谷歌)
- arm32 平台如果出错,参考 [issues #78](https://github.com/elecV2/elecV2P/issues/78)

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/03-rules.md
================================================
```
最近更新: 2022-03-24
适用版本: 3.7.8
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/03-rules.md
```

## 准备工作

- **再使用 RULES/REWRITE 相关功能前,请确定 ANYPROXY 已打开**
- 已正确将网络请求代理到 ANYPROXY 端口
- 匹配 https 请求请先添加 MITM host,普通 http 请求无需添加
- *首次命中 https 请求时,系统需要生成中间证书,可能会稍长一点时间返回结果*

## modify 规则集 格式说明

|   匹配方式   |    匹配内容(正则)   |  修改方式 |       修改目标      |  修改时间点
 :-----------: | --------------------- | :-------: | ------------------- | ----------
| url          | ^https://api.b.com/v2 | JS        | file.js             |  前(req)
| host         | api.bilibili.com      | useragent | iPhone 6s           |  后(res)
| useragent    | neteaseMusic / aliApp | block     | reject|tinyimg      |
| reqmethod    | GET/POST/PUT/DELETE   | $HOLD     | 30
| reqbody      | queryPara/word string |           |
| resstatus    | 200 / 404 / 301 / ... |           |
| restype      | text/html / text/json | -----     |
| resbody      | Keyword(string)       | all - JS  |

- *实际使用中匹配方式和修改方式可以任意搭配*

### 匹配方式

```
url             // 匹配 url 
host            // 匹配 url host 部分
useragent       // 匹配 User-Agent 
reqmethod       // 匹配 网络请求方式
reqbody         // 匹配 请求体(body)
resstatus       // 匹配 请求返回的状态码
restype         // 匹配 返回的数据类型
resbody         // 匹配 返回的数据内容
```

- **v3.7.8 默认不再对 reqbody/resbody 内容进行匹配,以提升 elecV2P MITM 效率。如需开启,请参考下文源文件格式部分,增加属性项 "enbody": true。(不匹配不代表不可以修改,仍然可以通过 url/host 等方式进行匹配,然后使用脚本对 body 内容进行修改)**

### 修改方式

#### JS

通过 JS 脚本修改网络请求数据,对应修改内容为 JS 文件名或远程 JS 链接。

从该模块运行 JS,默认会添加 $request,$response(**数据返回前**) 两个变量,具体参数如下:

- $request.headers, $request.body, $request.method, $request.hostname, $request.port, $request.path, $request.url
- $response.headers, $response.body, $response.statusCode

#### 307 重定向

对应修改内容为重定向目标网址

#### 阻止

reject: 返回状态码 200, body 为空。 
tinyimg: 返回状态码为 200, body 为一张 1x1 的图片

#### $HOLD

将原网络请求的 header 和 body 发送到前端网页进行修改处理,然后将修改后的数据直接发送给服务器/客户端。

对应修改内容表示等待前端修改数据的时间,单位秒。当为 **0** 时,表示一直等待。如果为其他值且超时时则直接使用原数据进行下步操作。

使用该修改方式时,请尽量使用比较详细的匹配规则,匹配单一网络请求,否则后面的 $HOLD 请求会覆盖前面的数据。

**2020.7.16 2.1.0 更新**

$HOLD request reject - 直接返回当前数据

返回默认状态码: 200

数据包含两部分: header 和 body

#### User-Agent

修改请求 header 中的 User-Agent。

默认 User-Agent 可在 webUI->SETTING->网络请求相关设置进行管理修改

### 修改时间

网络请求匹配时间

#### 网络请求前

beforeSendRequest

#### 数据返回前

beforeSendResponse

## 源文件格式

RULES 规则列表保存于 **./script/Lists/default.list**,实际格式为严格的 JSON 类型(不包含任何注释)。
*(参考: https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Lists/default.list )*

``` JSON
{
  "rules": {
    "note": "elecV2P RULES 规则列表",
    "enable": false,         // 是否启用下面列表中的规则,仅在该值为 false 时,表示不启用,默认启用
    "enbody": false,         // 是否对请求体(body)进行匹配,仅在该值为 true 时,表示启用(v3.7.8 添加,默认不启用
    "list": [
      {
        "mtype": "url",
        "match": "adtest",
        "ctype": "block",
        "target": "reject",
        "stage": "req"
      },
      {
        "mtype": "url",
        "match": "httpbin.org/get\\?hold",
        "ctype": "hold",
        "target": "0",
        "stage": "req",     // enable 可省略。仅在 enable 为 false 的时候表示不启用
        "enable": true
      }
    ]
  }
}
```

*如非必要,请不要手动修改 list 源文件*

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/04-JS.md
================================================
```
最近更新: 2024-11-10
适用版本: 3.8.1
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md
```

**每个脚本理论上都有权限对服务器上的任一文件进行修改,请不要运行不信任的脚本。**

## 保存目录

本地脚本物理存储目录位于 **./script/JSFile**,在 RULS/REWRITE/TASK/WEBHOOK 中调用时,直接使用对应的文件名即可。支持多级目录,比如 test/exam.js。

所有文件名称和内容可在 webUI->JSMANAGE/脚本管理 中查看和修改。

*如果远程脚本推送失败,尝试在 SETTING/设置->网络请求相关设置 中添加代理,或者下载后使用本地上传。*

## CONTEXT - 脚本运行环境

在 elecV2P 中,默认脚本的运行环境是基于 [vm](http://nodejs.org/api/vm.html) 模块的虚拟环境,同时增加了以下一些默认的环境变量及函数。

### 默认参数/环境变量


#### 主要函数

```
- $axios           // 网络请求
- $cheerio         // HTML处理
- $exec            // 简单 shell 命令执行
- $download        // 文件下载
- $feed            // 通知模块
- $store           // cookie/常量/数据存储
- $evui            // 在前端网页生成 UI 界面
- $message         // 发送网页消息
- $done            // 返回脚本执行结果(后面代码会继续执行)

- $cache           // 临时数据存储(v3.4.3 添加)
- $task            // 定时任务管理(v3.4.4 添加,sudo 模式下生效)
- $env             // 临时环境变量(v3.4.5 添加,默认包含 process.env 中的所有值)
- $fend            // efh 脚本前后端通信函数(v3.5.5 添加)
- $webhook         // 调用 webhook 接口(v3.5.8 添加,sudo 模式下生效)
```

可利用这些函数判断当前脚本是否运行在 elecV2P 中,比如:

``` JS
if (typeof $fend !== 'undefined') {
  console.log('elecV2P 运行环境')
} else {
  console.log('其他运行环境')
}
```

#### 附加变量 (以两个短下划线开头)

```
- __version        // 当前 elecV2P 版本
- __vernum         // 当前版本数字表达(v3.4.5 添加)。比如版本 3.4.5 表达为 345
- __home           // 主页地址。 可在 webUI->SETTING 界面设置
- __efss           // efss 目录。 可在 webUI/efss 页面设置
- __name           // 脚本名。 v3.3.0 添加 (如果是多级目录脚本,会包含目录)
- __dirname        // 脚本所在目录 (v3.4.4)
- __filename       // 脚本完整路径 (v3.4.4)
- __userid         // 用户 ID (v3.6.4)
- __md5hash        // 当前脚本内容的 md5 hash 值(v3.6.7)

- __taskid         // 启动该脚本的任务 id (仅在定时任务触发脚本时有值,其他时候为 undefined)
- __taskname       // 启动该脚本的任务名称 (仅在定时任务触发脚本时有值,其他时候为 undefined)

// 测试 JS:
// console.log('dirname:', __dirname, 'version:', __version, 'homepage:', __home)
// console.log('当前 efss 目录:', __efss, '当前脚本名称:', __name)
// if (__vernum >= 367) console.log('当前脚本 md5 hash:', __md5hash)
```

#### 特殊变量函数

```
- $ws              // 通过 websocket 向前端发送数据
- $request/$response
- require()        // 直接引用其他 nodejs 模块或脚本
- console.clear()  // 清空该脚本的运行日志
- // @grant        // 在单个脚本开启增强功能
```

### @grant(v3.8.1 后取消脚本环境兼容性判断

JS 文件开头使用 **// @grant** 开启一些增强功能

*如果要使用纯 nodejs 环境运行,在 TASK 中请选择 shell 指令模式,然后: node xxxxx.js。 或者在脚本中使用 $exec('node xxxx.js') 来执行*

```
// @grant nodejs 脚本和直接使用 node xxxx.js 运行脚本的区别:

- 使用 node xxxxx.js 无法使用 $axios/$store/$feed 等非 nodejs 原生函数/变量
- 默认脚本运行在 vm 的虚拟环境中,而 node xxxx.js 运行在原生系统环境中
```

日志调整

* **// @grant  calm**    ;不打印日志,但保留到日志文件中,也不影响通知(console.error 错误日志还是会正常打印)
* **// @grant  still**   ;不输出日志,有通知(即 console 函数无效)
* **// @grant  quiet**   ;输出日志,但不通知
* **// @grant  silent**  ;不输出日志,也不通知

#### sudo 模式(v3.4.4)

* **// @grant  sudo** 

sudo 模式下启用功能

- $task  ;定时任务管理(v3.4.4)
- $webhook   ;调用 webhook 接口功能(v3.5.8)
- 其他功能待添加

### $axios - 网络请求

$axios(request)

request 格式 [object/string]
- object:支持参数参考:[axios](https://github.com/axios/axios)
- string:单个 url 链接

``` JS
// --- example 1 ---
$axios('https://httpbin.org/get?hello=elecV2P').then(res=>console.log(res.data)).catch(e=>console.log(e))

// --- example 2 ---
$axios({
  url: 'https://httpbin.org/put',
  method: 'put',
  timeout: 6000,
  data: {
    hello: 'elecV2P'
  }
}).then(res=>console.log(res.data)).catch(e=>console.log(e.message))
```

* **$axios** 无 .put/.post 等方法,使用 { ..., method: 'put/post' } 实现

**v2.1.8 更新 $axios(request, proxy)**

增加第二个参数 proxy, 此参数会覆盖 request.proxy, 示例

``` JS
$axios(request, {
  host: '127.0.0.1',
  port: 9000,
  auth: {
    username: 'hello',
    password: 'elecV2P'
  }
}).then(res=>console.log(res.data)).catch(e=>console.log(e.message))

// 当 proxy 为 {} 时使用内部 ANYPROXY 代理,为 false 时: 强制跳过使用代理,如省略则使用 webUI->SETTING 网络请求相关设置。
// 其他设置参考 [axios](https://github.com/axios/axios) request/proxy 部分
```

如在运行脚本时需要访问某些境外网站,可在 webUI->SETTING 网络请求相关设置 中添加代理。

### $store - store/cookie 常量

所有存储常量以文件的形式保存在 **script/Store** 目录中。
所有脚本共用存储常量,比如在 a.js 中使用 $store.put('something', 'akey'), 可在 b.js 中使用 $store.get('akey') 来获取存储值。

``` JS example
$store.get(key, options)             // 获取存储值
// options 可选
// - string 字符串
//   - 'raw': 获取存储文件源内容
//   - 'string/array/object/boolean/number': 以相应格式获取存储值
// - object (v3.6.6 新增)
//   {
//     type: 'string',       // 可选值同上面字符串
//     pass: 'string',       // 获取加密内容时密码
//     algo: 'ebuf',         // 获取加密内容时算法,可省略。默认为 ebuf 自定义算法
//   }

$store.put(value, key, options)    // 保存
// options 可选
// - string 字符串
//   - 'a': 添加内容。具体参考下面的 JS 实例
//   - 'string/array/object/boolean/number': 保存为对应格式
// - object (v3.3.3 新增)
//   {
//     type: 'a',            // 可选值同上面字符串
//     note: '备注信息',     // 关于 cookie 的一些说明,可省略
//     belong: 'test.js',    // 该 cookie 的归属脚本(调用或写入该 cookie 的脚本),可省略
//     pass: 'string',       // 加密存储内容时的密码(v3.6.6 新增)
//     algo: 'ebuf',         // 加密存储内容时的算法(v3.6.6 新增),可省略。默认为 ebuf 自定义算法
//   }
$store.set(key, value, options)   // 等同于将上面的 $store.put 交换 key 和 value 的位置。(v3.4.5 添加)

$store.delete(key)                // 删除
```

$store 保存时会对数据类型进行简单的判断,当数据类型为 **number/boolean/array/object** 时,会按照数据类型进行保存。

``` JS store 实例
$store.put(123, 'number')       // put 方法,第一个参数为保存值 value, 第二个参数为关键字 key
typeof $store.get('number')     // get 方法通过关键字获取保存值,返回数字 123,类型为 number

$store.get('number', 'raw')     // 返回 { "type": "number", "value": 123 }

$store.set('newarr', [2,3,4])   // 存储为数组。保存成功返回 true,失败返回 false。 set 方法第一个参数为 key,第二个参数为 value
$store.get('newarr')            // 返回数组: [2,3,4]

$store.put(5, 'newarr', 'a')
// 根据 newarr 原来保存值的类型,存储新的结果
// 原 newarr 值为 array 数组,则新保存值为: [2,3,4,5]
// 如果原 newarr 的值为数字,比如 8,则新的存储值 13
$store.put([6,7], 'newarr', 'a')   // newarr 新值为:[2,3,4,5,6,7]

$store.put('a string', 'keystr')   // 不指定类型时 直接保存为 string 的形式
$store.put('add new line', 'keystr', 'a')    // 在原 keystr 的值后面添加新行
$store.get('keystr', 'raw')
// 返回:
// {"type":"string","value":"a string\nadd new line"}

$store.put({a: 334, b: 'abc'}, 'keyobj')     // 存储类型为 object。
$store.get('keyobj')               // 返回: {"a":334,"b":"abc"}
$store.get('keyobj', 'raw')                  // 返回: {"type":"object","value":{"a":334,"b":"abc"}}
$store.put({c: 'new val'}, 'keyobj', 'a')    // 新存储的值为: {"a":334,"b":"abc","c": "new val"}

$store.put('eoooe', 'keybol', 'boolean')     // 强制转化为 boolean 值: true
// 建议在使用 $store.put 并指定 type 的时候,先确定原存储数值和即将保存数值的类型,以免发生未知错误。

$store.delete('number')           // 删除某个 store/cookie 常量 number
// 等同于 $store.put('', 'number')

// 特殊情况
$store.put('a string 字符', 'objstr', 'object')   // 强制将字符串保存为 object 格式。存储值为: {0: "a string 字符"}。 PS: array 是同样结果
$store.get('keystr', 'array')     // 将字符串以 object 格式取出。结果为: {"0":"a string"}

// v2.5.2 更新 $store.get type random。 type 关键字 random 或者 r
$store.get('newarr', 'random')    // 返回 newarr 数组中的一个随机值
$store.get('number', 'r')         // 返回 0 - number 代表值 中间的任一整数
$store.get('keyobj', 'r')         // 返回 object 中的任一 keys 对应值
$store.get('keybol', 'random')    // 返回 随机 true/false
$store.get('keystr', 'r')         // 如果 keystr 存储的是字符串,取随机值时,取随机一行的数据

// v3.3.3 更新: $store.put 第三个参数改为 options
// 当 options 为字符串类型时表示为之前的普通 type 类型
// 当 options 为 object 类型时,type/note/belong 关键字分别表示该 cookie 的 类型/备注/关联脚本
$store.put('a string 字符', 'objstr', {
  type: 'string',
  note: '关于这个 cookie 一些备注说明',
  belong: __name + ', store.js',            // 调用/写入这个 cookie 的脚本
})

// v3.6.6 更新 加密存储
$store.put('待加密的内容', 'tstr', {
  pass: '加密密钥 password',
})
// 使用对应密钥获取加密内容
$store.get('tstr', {
  pass: '加密密钥 password',
})

// 普通 get,获取到的数据是加密后的内容
$store.get('tstr')

$store.put({ a: '加密其他类型的数据,比如 object' }, 'tobj', {
  type: 'object',
  pass: $env.STORE_KEY || 'testkey',  // 可配合环境变量隐藏使用密钥
})
```

### $cache - 临时数据存储 (v3.4.3)

用于存储脚本运行时的一些临时数据,在 elecV2P 重启后会自动丢弃。
所有脚本共享临时数据,比如在 a.js 中使用 $cache.hello = 'elecV2P', 可在 b.js 中使用 $cache.hello 来获取临时值。

共有五个方法: get(key), put(value, key), delete(key), keys(), clear()
v3.4.5 添加方法: set(key, value)  // 和 put 方法的 key/value 顺序相反

``` JS
$cache.v = 'hello elecV2P'       // 添加临时变量 v
// 等同于
$cache.put('hello elecV2P', 'v')   // put 方法 value 在前,key 在后
$cache.set('vv', '你好,elecV2P')  // set 方法 key 在前,value 在后(v3.4.5 添加)

let val = $cache.get('v')        // 获取临时变量 v 中的内容
// 等同于 let val = $cache.v

$cache.delete('v')               // 删除临时变量 v
console.log($cache.v, $cache.vv)            // 如果临时变量不存在,将会返回 undefined

$cache.obj = {                   // 临时变量存储对象可以是任意值。包括 object 和 函数等
  num: 1234,
  s(){
    console.log('$cache function', this.num++)
  }
}
$cache.obj.s()
console.log($cache.obj.num)

let keys = $cache.keys()         // 以数组的形式返回当前所有临时变量关键字。
// [ 'obj' ]

$cache.clear()                   // 清空存储的临时变量
console.log(keys, $cache.keys())

console.log($cache.size)         // 当前临时变量个数。(v3.4.5 添加)
```

**$cache** 和 **$store** 的区别:
- $cache 数据在内存中读写,$store 数据在硬盘上读写(因此,$cache 存取速度更快,但会占用一些内存,不建议保存大量数据)
- $cache 是临时存储,在 elecV2P 重启后所有数据会丢失。$store 是常量存储,重启后已存储数据依然存在
- $store 只能通过 get/put 方法来存取数据,而 $cache 可以直接通过点引用来存取。(比如: $cache.haha = '哈哈哈哈哈哈哈')
- $store 没有 keys()/clear()/size 的方法/属性,也不能保存函数类'动态'数据

### $env - 临时环境变量 (v3.4.5)

``` JS
console.log($env)
console.log('version', $env.version)

let key = 'name'
if ($env[key]) {
  console.log($env[key])
} else {
  console.log('当前环境变量中暂无', key, '相关值')
}
```

- 临时环境变量默认包含 process.env 中的所有值,不管当前是否以 nodejs 兼容模式运行
- 临时环境变量仅在当前脚本中有效。比如在 a.js 中设置 $env.version = '1.0.0', 在 b.js 中 $env.version 还是等于 process.env.version
- process.env 的变化会反应到 $env 中,但 $env 的变化不会影响 process.env 的值
- 在使用定时任务或 webhook 等方式运行脚本时,可使用 -env 添加临时环境变量(具体参考 [06-task.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/06-task.md) 运行 JS 相关部分)
- v3.7.6 增加 $env.lang 获取当前设置的语言偏好,比如 en | zh-CN

### $feed - 通知模块

发送一条通知
- $feed.push(tile, description, url)
  - title: 通知标题。 如省略,则会使用 'elecV2P 通知' 替代
  - description: 通知内容。 如省略,则会显示 'a empty message\n没有任何通知内容'
  - url: 点击通知后的跳转链接。可省略

``` JS example
// PUSH 通知包括 RSS 和其他手动设置好的通知方式
$feed.push('elecV2P notification', '这是一条来自 elecV2P 的通知', 'https://github.com/elecV2/elecV2P')

// 单独发送一条 ifttt 通知
$feed.ifttt('elecV2P 通知', '一条来自 $feed.ifttt 的通知', 'https://github.com/elecV2/elecV2P-dei')

// 单独发送一条 bark 通知
$feed.bark('elecV2P 通知', '一条来自 $feed.bark 的通知', 'https://t.me/elecV2')

// 在 title 开头添加 $enable$ 强制发送通知
$feed.cust('$enable$自定义通知', '使用 $enable$ 强制发送的一条通知', 'https://github.com/elecV2/elecV2P-dei/tree/master/docs/07-feed&notify.md')
```

- ifttt/bark 等通知需提前在 webUI->SETTING 页面设置好 TOKEN/KEY
- 如果 SETTING 相关通知为关闭状态,则调用了也不会有通知

更多相关说明参考: [07-feed&notify](https://github.com/elecV2/elecV2P-dei/tree/master/docs/07-feed&notify.md)

### $exec - Shell 指令执行函数

*Shell 指令的运行基于 nodejs 的 [child_process_exec](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) 模块*

#### 基础使用: $exec(command, options = { cwd, env, timeout, call, cb, stdin })

*options 可省略,opitons 中的每一项参数也都可省略*

- cwd: (string) 工作目录
  - 当没有设置或设置目录不存在时,如果是 node 命令开头,默认为: script/JSFile,其他情况默认为: script/Shell
  - v3.4.2 之前默认工作目录为: process.cwd()
- env: (object) 环境变量 (默认为: process.env)
- timeout: (number)  超时时间。单位: 毫秒(ms),0: 表示不设定超时时间。默认为 60000ms(60秒)
- call: (boolean) 是否在命令执行完成后返回所有输出内容
- cb(data, error, finish):  (function)  回调函数
  - data: stdout.on('data')   命令执行时的输出内容
  - error: stderr.on('data')  命令执行出错时的输出内容
  - finish: exec.on('exit')   命令执行完毕信号,最终返回 true
- stdin: (object) { write, delay } 延时输入交互数据(v3.2.6 增加)
  - write: (string) 延时写入的数据
  - delay: (number) 延时时间。单位: 毫秒(ms),可省略(默认为 2000)

``` JS example
$exec('ls', {
  cwd: './efss',      // 命令执行目录
  timeout: 5000,
  cb(data, error){
    error ? console.error(error) : console.log(data)
  }
})

// 如果省略 options 中的所有参数,那么对应输出只能在后台看到
$exec('node -v')

// 在 Docker 环境在安装 python3,并执行其他 python 文件
$exec('apk add python3', {
  timeout: 0,
  cb(data, error, finish){
    if (finish) {
      // 安装完以后可以直接在 JS 中调用。(pyhton 和库安装完成后可在其他脚本中直接调用,不需要再次安装。)
      $exec('python3 -u test.py', {
        cwd: './script/Shell',      // test.py 文件放置的目录。可修改为其他目录,比如 './efss'
        cb(data, error){
          error ? console.error(error) : console.log(data)
        }
      })

      // 安装一些 python 库
      $exec('pip3 install you-get youtube-dl numpy requests')
    } else {
      error ? console.error(error) : console.log(data)
    }
  }
})

// stdin 延迟输入交互内容 简单示例
$exec('python3 -u askinput.py', {
  cwd: './script/Shell',
  stdin: {
    delay: 3000,   // 输入延时时间,单位 ms。可省略
    write: 'elecV2P\nI am fine, thank you.'     // 具体输入数据。(根据实际情况进行修改)
  },
  cb(data, error){
    if (error) {
      console.error(error)
    } else {
      console.log(data)
    }
  }
})

/* askinput.py 内容
name = input("what is your name?") 
print('nice to meet you,', name)
greet = input(f"how are you, {name}?")
print(greet)
 */

// 特别使用: 给当前执行命令设置单独日志文件
$exec('ls', {
  cwd: './efss',           // 命令执行目录
  logname: 'ls执行日志',   // 命令执行日志保存文件名,可自定义为其他值
  from: 'task',            // v3.4.5 之前此项必需,且只能为 'task'。v3.4.5 之后可省略
  // 当 from 设置为 task 时,实时日志可在 TASK 定时任务界面查看
  // cb 函数可省略。日志记录保存在 logname 设置的文件中
})
```

*如果命令不可执行,尝试先在系统命令行工具下手动输入命令,进行测试*

**如果 windows 平台出现乱码,尝试命令 *CHCP 65001*。或者修改注册表 Active code page 为 65001(具体操作,善用搜索)** (*尝试过 iconv 转换(< v1.8.2),弃用*)

#### **v3.2.8 增加支持运行远程文件**

``` JS
// 例如:
$exec('python3 -u https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Shell/test.py', {
  cwd: './script/Shell',      // 命令执行目录
  timeout: 5000,
  cb(data, error){
    error ? console.error(error) : console.log(data)
  }
})
/* 说明:
- 当执行命令(command)中包含远程链接时,会自动下载远程文件到 cwd 目录,并将该远程链接替换为下载后的文件地址
  - 比如,上面的代码会先下载远程文件 test.py 到 script/Shell 目录,然后命令自动转化为 python3 -u /xxx/script/Shell/test.py
- 远程链接匹配方式: command.match(/ (https?:\/\/\S{4,})/)
- 远程文件执行时默认每次都会重新下载
  - 可使用 options.rename: xxx 来重命名下载文件
  - 可使用 options.local: true 来跳过下载远程文件,直接以本地文件运行(如果存在的话,如果不存在还是会远程下载
- 如果远程文件下载失败,将会尝试运行本地文件
- 如果不希望对原命令中的远程链接进行下载及替换操作,可使用 options.nohttp: true, 或使用 -http 进行转义
  - 注意: 命令中的所有 ' -http' 字符(不含引号)都会被替换为 ' http'
- 以下常用命令已排除远程下载及替换操作:
  - curl/wget/git/start  (v3.2.9)
  - you-get/youtube-dl   (v3.3.0)
  - aria2c/http/npm/yarn/ping/openssl/telnet/nc/echo    (v3.4.2)
  - *如果还有其他的常用网络相关命令,欢迎反馈添加*
**/

// 以 git 等其他常用命令开头,远程链接不会进行自动转换,按原命令处理
$exec('git clone https://github.com/elecV2/elecV2P', {
  cwd: 'script/JSFile',
  cb(data, error, finish){
    error ? console.error(error) : console.log(data)
    if (finish) {
      console.log('git clone 完成')
    }
  }
})

// 其他命令中如果包含远程链接,将会自动下载,并将命令中的远程链接替换为文件下载后的地址
$exec('node https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js', {
  // 将自动下载 webhook.js 到 cwd 目录('script/JSFile'),并将远程链接替换为文件下载后的地址 node /xxx/script/JSFile/webhook.js
  cwd: 'script/JSFile',
  local: true,     // true: 当 cwd 目录下存在 webhook.js 时,直接使用本地文件运行,不进行远程下载
  // rename: 'testremote.js',  // 重命名远程下载文件
  cb(data, error){
    error ? console.error(error) : console.log(data)
  }
})

$exec('echo -http://127.0.0.1')   // 输出结果为: http://127.0.0.1
// 等同于
$exec('echo http://127.0.0.1')    // echo 命令不会下载远程文件及替换

// 使用 cat 命令下载并查看远程文件
$exec('cat http://127.0.0.1/efss/readme.md', {
  // 如没有指定 cwd,或指定 cwd 目录不存在时,则使用默认 cwd: script/Shell
  // nohttp: true,   // 启用此项,表示不转化 http 远程链接,将会搜索文件 'http://127.0.0.1/efss/readme.md',然后直接报错
  // local: true,    // 启用此项,表示如果 cwd 目录中存在 readme.md 文件,则不下载(但远程链接会替换为本地文件地址)
  cb(data, error){
    error ? console.error(error) : console.log(data)
  }
})
```

### $cheerio - HTML处理

用于对 html 的处理

``` JS example
// example #1
let body = $response.body
let restype = $response.headers['Content-Type']

if (/html/.test(restype)) {
  const $ = $cheerio.load(body)
  $('body').text('hello cheerio')
  body = $.html()
  console.log(body)
}

$done(body)

// example #2
const $ = $cheerio.load(`<ul id="fruits">
  <li class="apple">Apple</li>
  <li class="orange">Orange</li>
  <li class="pear">Pear</li>
</ul>`);

const apple = $('.apple', '#fruits').text()
console.log(apple)

const attr = $('ul .pear').attr('class');
console.log(attr)

const html = $('#fruits').html();
console.log(html)

$done($('.pear').text())
```

更多使用方法参考:[cheerio](https://github.com/cheeriojs/cheerio) 官方说明文档

### $download - 文件下载

用于直链文件下载,可指定下载目录。如不指定下载目录则保存到默认目录。
默认保存目录为 efss 虚拟目录,如果 efss 目录为空则保存到 web/dist 目录。

基础用法:
$download(url, options).then(d=>console.log(d)).catch(e=>console.error(e))

**options** 变量说明:
- 直接省略,表示使用默认目录保存文件
- 字符类型,分两种情况
  - 字符表示的是一个已存在的文件夹,则下载到该文件夹,并以 url 的结尾命名文件
  - 否则表示的是 **目录(如有)+文件名**
- 对象类型,可接受四个参数: { folder, name, existskip, timeout }
  - folder 表示下载目录
  - name 表示文件名(其中也可包含目录)
  - existskip 当目标文件已存在时不下载(v3.4.9 添加)
  - timeout 本次下载的超时时间,单位 ms(v3.5.0 添加)

下面以几个具体实例进行说明:

``` JS example
$download('https://raw.githubusercontent.com/elecV2/elecV2P/master/Todo.md').then(d=>console.log(d)).catch(e=>console.error(e))

// 指定下载目录及文件名
$download('https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/Shell/exam-request.py', './script/Shell/myreq.py').then(d=>console.log(d)).catch(e=>console.error(e))
// 前面一部分 script/Shell 表示保存目录,后面的 myreq.py 表示文件名
// 如果仅有 myreq.py 的话,文件会以该名字保存到默认目录

// 以 object 方式指定下载目录
$download('https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/boxjs.ev.js', {
  folder: './script/JSFile',
  name: 'box.js',
  existskip: true,
}).then(d=>console.log('文件已下载至: ' + d)).catch(e=>console.error(e))
// 假如将 name 修改为 'test/box.js', 此时文件名中同时包含目录,则会在 script/JSFile 下新建目录 test,然后将文件(box.js)保存到 test 中
```

#### 特别使用:获取下载进度

$download(url, options, cb) - 前两项参数不变,增加第三个参数为 callback 函数,callback 函数在下载期间会被多次调用。

cb(options = {}),传入的 options 参数有如下值:

- name<string>: 下载的文件名称
- progress<string>: 下载的进度条
- chunk<number>: 第 n 个下载块(仅在下载中存在
- dsize<number>: 文件已下载大小(downloaded size (v3.7.2 增加
- total<number>: 文件总大小(对应为 response.headers['content-length'] 项,不存在时为 NaN(v3.7.2 增加
- start<string>: 开始下载(仅开始下载时存在(对应内容为下载文件的完整保存路径(v3.7.2
- finish<string>: 下载完成后的消息(仅在下载完成后存在(对应内容为下载后文件的完整路径(v3.7.2

*start 和 finish 内容一样,都表示文件下载到 elecV2P 服务器的绝对路径,只是存在阶段不一样*

示例:

``` JS
$download('https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/Shell/elecV2P.py', {
  folder: 'script/Shell',
  name: 'elecV2P.org.py'
}, (options)=>{
  // callback 函数,支持异步函数
  if (options.start) {
    // 下载开始时存在的参数 opitons.name/progress/chunk/dsize/total/start
    // 开始阶段 chunk<=>dsize<=>0
    console.log(`开始下载文件 ${options.name} 到 ${options.start}, 文件大小 ${kSize(options.total)}`)
  } else if (options.finish) {
    // 下载完成时存在的参数 opitons.name/progress/dsize/total/finish
    // 完成阶段 dsize<=>total<=>response.headers['content-length'] || NaN
    $message.success(`${options.name} 下载完成 ${options.total ? '总大小 ' + kSize(options.total) : ''}`)
    console.log(options.name, '已下载至', options.finish)
  } else {
    // 下载中存在的参数 opitons.name/progress/chunk/dsize/total
    options.chunk % 100 === 0 && $message.success(`正在下载 ${options.name} 第 ${options.chunk} 块数据 ${kSize(options.dsize)}/${kSize(options.total)}`, { mid: 'download' })
    // console.log(options.progress + '\r')    // 显示下载进度条。(后面的 '/r' 用于在前端页面删除上一条 log 日志)
    console.log(options.progress, '\x1b[F')    // v3.7.2 后推荐使用该条代码
  }
}).then(d=>console.log(d)).catch(e=>console.error(e))

function kSize(size, k = 1024) {
  if (size < k) {
    return size + ' B'
  }
  if (size < k*k) {
    return (size/k).toFixed(2) + ' K'
  }
  if (size < k*k*k) {
    return (size/(k*k)).toFixed(2) + ' M'
  }
  return (size/(k*k*k)).toFixed(2) + ' G'
}
```

### $evui - 生成一个 UI 界面

*$evui 的参数传递基于 websocket*

可接收两个参数:$evui(option, callback)
- option: UI 界面相关参数
- callback: 用于接收处理 UI 界面提交返回的数据。(可省略)

$evui 返回的是一个 Promise 函数
- resolve 条件: 当 cbable 为 true 时,直到前端关闭窗口。否则,直接 resolve
- reject 条件: 传递参数有误或者 websocket 尚未连接

``` JS
const ui = {
  id: 'ebcaa4ff',      // 给图形界面一个独一无二的 ID。可省略(以下所有参数都可省略,不再重复说明)
  title: 'elecV2P windows',          // 窗口标题
  width: 800,          // 窗口宽度
  height: 600,         // 窗口高度。null 表示自适应高度
  content: `<p>显示一张图片</p><img src='https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/overview.png'>`,            // 图形界面显示内容
  style: {             // 设置一些基础样式
    title: "background: #6B8E23;",   // 设置标题样式
    content: "background: #FF8033; font-size: 32px; text-align: center",  // 设置中间主体内容样式
    cbdata: "height: 220px;",        // 设置返回数据输入框样式
    cbbtn: "width: 220px;"           // 设置提交数据按钮的样式
  },
  resizable: true,     // 窗口是否可以缩放
  draggable: true,     // 窗口是否可以拖动
  cbable: true,        // 是否启用 callback 函数,用于接收前端 UI 提交返回的数据
  cb(data){            // callback 函数。此项会被 $evui 的第二个参数覆盖(如有)
    console.log('data from client:', data)
  },
  cbdata: 'hello',     // 提供给前端 UI 界面的初始数据
  cblabel: '提交数据', // 提交按钮显示文字
  script: `console.log('hello $evui');alert('hi, elecV2P')`,     // v3.2.4 增加支持在前端网页中插入 javascript 代码
}

$evui(ui, data=>{
  // 此为 callback 函数,用于接收处理前端 UI 返回的数据,可省略。如有则会覆盖前一项参数中的 cb 变量(ui.cb)
  if (data == 1) {
    $feed.push('Get a infomation frome $evui', 'message:' + data)
  } else if (/^exec /.test(data)) {
    let command = data.split('exec ').pop()
    $exec(command, {
      cb(data, error){
        console.log(error || data)
      }
    })
  } else {
    console.log('data from client:', data)
  }
}).then(data=>console.log(data)).catch(e=>console.error(e))

// 发送关闭前端 evui 界面的指令 (v3.4.0 增加)
$ws.send({ type: 'evui', data: { id: 'ebcaa4ff', type: 'close' }})

// * $ws.send 函数可临时用于服务器通过 websocket 向前端发送数据
```

效果:

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/evuitest.png)

### $message - 给前端网页发送一条消息

*消息传递基于 websocket*

共有三种方法:
- success  成功
- error    错误
- loading  加载中

v3.4.2 增加方法
- close    用于关掉网页消息体

``` JS
// 基础使用
$message.success('一条来自脚本的消息')
// $message 可接受任意个参数(v3.4.0)
// 当最后一个参数为数字时,表示消息显示时间,单位:秒。
// 最后参数为 object 且包含 secd/url/mid/align 等参数时:
// - secd 消息显示时间,单位: 秒(默认: 消息长度/5 + 已有消息数*3 秒)。 0: 一直显示不关闭
// - url  点击消息后跳转 url(当为 reload 或 refresh 时表示点击强制刷新当前网页 v3.5.2)
// - mid  消息体的 id (v3.4.2 增加)
// - align  消息文字对齐方式(v3.4.4 增加)。默认: center,可设置 left: 左对齐,right: 右对齐。其他值使用默认 center

$message.error('some wrong is happen', 10)
// 一条错误提醒消息,10 秒后自动关闭
// 第二个参数如果是 0: 表示消息不自动关闭

$message.loading('等待中...', 0)

// 最后一个参数为 object 且传递 secd 和 url
$message.success(23, '参数类型和数量不限', true, { hello: 'elecV2P' }, '点击消息\n可打开 elecV2P Github 主页', { secd: 0, url: "https://github.com/elecV2/elecV2P" })

// v3.4.2 增加关掉前端网页已弹出消息体的功能
$message.loading('等待关闭中...\n请点击消息右侧进行关闭', {
  secd: 0,       // 0: 不主动关闭
  mid: 'auid',   // mid 对应值随意填写
  align: 'left', // 文字左对齐
})

$message.close('auid')  // 关掉 mid === 'auid' 的消息体
$message.close() // 关掉所有已弹出的消息体
```

### $ws - 向前端发送临时数据

一般用于需要异步或持续向前端发送数据的特殊情况。

- $ws.send({ type, data })  通过 websocket 向前端发送数据
- $ws.sse(sseid, data)  通过 server-sent events 向前端发送数据(v3.7.2 添加)

$ws.send 可使用的 type 类型已在前端定义,以后会整理开放。该方法仅在前端 webUI websocket 已连接的情况有效。通常用于 evui 界面通信中,参考脚本:https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/evui-chatroom.js

$ws.sse 需要先在前端生成 new EventSource('/sse/elecV2P/' + sseid) 事件,然后可以在事件上自定义数据接收后的处理函数。通常用于 efh 脚本中,参考脚本:https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/efh/kuwo-music.efh (相关资料:[Using server-sent events - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))

**这两个函数发送的消息/数据,会同步发送到所有已连接的客户端**

### $request/$response

$request: 存在于通过 RULES/REWRITE/favend 触发的脚本中
$response: 存在于通过 RULES/REWRITE 触发,并且修改时间段为数据返回前的脚本中

*有 $request 不一定有 $response, 但有 $response 一定有 $request*

- $request.url<string> : 网络请求完整 URL
- $request.headers<object> : 不区分大小写。比如 headers.Host === headers.host ==== headers.HOST
- $request.body<string|buffer> : 通常为 string 类型,buffer 类型的条件见下面说明
- $request.bodyBytes<buffer> : 原始 buffer 数据。 favend 模式下该参数不存在
- $request.method<string> : GET|POST|PUT|DELETE 等值,大写
- $request.protocol<string> : http|https 等值,小写
- $request.hostname<string> : 请求域名/IP,比如 127.0.0.1
- $request.port<number> : 请求端口,比如 80|443|8000 等
- $request.path<string> : 请求路径
- $request.pathname<string> : 请求路径,同上

- $response.status<number> : 网络请求返回状态码。同下
- $response.statusCode<number> : 网络请求返回状态码,比如 200|301|404 等
- $response.headers<object> : 不区分大小写。比如 headers['Content-Type'] === headers['content-type']
- $response.body<string|buffer> : 通常为 string 类型,buffer 类型的条件见下面说明
- $response.bodyBytes<buffer> : 原始 buffer 数据

**当满足 /^(audio|video|image|multipart|font|model)|(ogg|stream|protobuf)$/.test(headers['Content-Type']) 条件时,$request.body/$response.body 对应类型为 buffer(v3.7.5 增加)**
**$reponse 是服务器返回给 MITM 代理的数据,最终返回给客户端的数据以 $done 结果为准**

``` JS example
// 可使用的相关参数
// $request.url<string>, $request.headers<object>, $request.body<string|buffer>, $request.bodyBytes<buffer>
// $request.method<string>, $request.protocol<string>, $request.hostname<string>, $request.port<number>, $request.path<string>
// $response.status<number>, $response.statusCode<number>, $response.headers<object>, $response.body<string>, $response.bodyBytes<buffer>

console.log('$request', $request)

let body = $response.body
// let obj = JSON.parse(body)
if (/httpbin/.test($request.url)) {
  body += 'change by elecV2P'
}
$done({ body })
```

- v3.4.8 添加 **$request.bodyBytes/$response.bodyBytes**,对应数据类型为 **Buffer**
- 更多相关说明,可参考脚本 https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/0body.js

### $task 定时任务管理(v3.4.4)

该功能仅在开启 sudo 模式的脚本中有效。(sudo 模式开启方式: 在脚本中添加 **// @grant  sudo**)

$task 拥有的方法/函数:

- add(taskinfo<object>[, options])  ;添加定时任务
- start(taskid<string|array>)   ;开始某个/些定时任务
- stop(taskid<string|array>)    ;停止某个/些定时任务
- delete(taskid<string|array>)  ;删除某个/些定时任务
- info(taskid<string>)   ;获取某个任务的信息。当省略 taskid 时,返回所有任务信息
- nameList()  ;获取任务名及对应 taskid 列表<object>
- status()    ;返回当前任务数。总任务/运行中的任务/订阅任务数
- save()      ;保存当前任务列表。(用于在 elecV2P 重启后自动恢复)

具体使用

``` JS
// @grant  sudo

// 查看当前任务数
console.log($task.status())

// 添加任务(具体任务格式参考 https://github.com/elecV2/elecV2P-dei/blob/master/docs/06-task.md)
let res = $task.add({
  name: '$task 添加的任务',
  type: 'cron',
  time: '12 15 18 * * *',
  job: {
    type: 'exec',
    target: 'pm2 ls'
  }
})
console.log('$task 添加任务结果', res)
// 如果需要添加多个任务,可使用 array 数组的形式
// 比如 $task.add([{}, {}], { type: 'replace' })
// 第二个参数 options 可省略。
// 如设置 type, 表示同名任务的更新方式。有三个有效值:
// - replace    替换原同名任务
// - addition   新增同名任务
// - skip       跳过添加同名任务

// 获取任务名及对应 taskid
let tnlist = $task.nameList()
console.log(tnlist)
// 返回的是类似于 { '任务名': taskid } 的 object
// 通过该 object,可使用任务名快速查找任务 id
// 如果任务列表不经常变化的话建议使用 $store.put 或 $cache 保存

// 开始任务
console.log($task.start('m8LWPxDc'))

// 停止任务
console.log($task.stop(tnlist['$task 添加的任务']))   // 通过任务名查找任务 id

// 删除任务
console.log($task.delete('mkl7pwQn'))

// 使用数组的形式传入 taskid,可批量开始/暂停/删除 定时任务
$task.start(['m8LWPxDc', 'ataskid', tnlist['$task 添加的任务'], 'jxwQOSJZ'])
// $task.stop(['taskid1', 'taskid2', ...])
// $task.delete(['taskid1', 'taskid2', ...])

// 查看某个任务信息
let taskinfo = $task.info('m8LWPxDc')   // 查看所有任务信息 $task.info() 或者 $task.info('all')
console.log(taskinfo)

// 查询 __taskid/__taskname (仅在使用定时任务运行脚本时有值,其他情况默认为 undefined
console.log('执行该脚本的任务名:', __taskname)
console.log('相关任务信息', $task.info(__taskid))

// 尝试使用 __taskid 来停止自身定时任务
if (__taskid) {
  let stopinfo = $task.stop(__taskid)   // 停止自身定时任务
  console.log(stopinfo)
}

// 保存当前任务列表
let saveres = $task.save()
console.log(saveres)
```

### $webhook 调用 webhook 接口(v3.5.8)

*$webhook 的本质是一个对 localhost/webhook 发起的 POST 网络请求*

使用前提:

- 脚本中开启 sudo 模式
- 127.0.0.1 IP 没有被限制

基础格式:$webhook(type<string|object>, options<object>)

- 当 type 为字符串时,表示执行 webhook 类型(具体参考 https://github.com/elecV2/elecV2P-dei/blob/master/docs/09-webhook.md )
- 当 type 为 object 时,表示 POST 请求的 body(无需 token)
- options 可省略,当为 object 时表示附带参数
- 最终 POST 的 body 为 Object.assign({ type }, options)
- 为避免循环调用,通过 webhook 调用的脚本 $webhook type runjs 将不可用

使用示例:

``` JS
// @grant sudo
$webhook('status').then(res=>console.log(res.data));
// 完全等同于
$webhook({ type: 'status' }).then(res=>console.log(res.data));

// 附带 options
$webhook('info', {debug: true})         //  $webhook('runjs', { fn: 'test.js' })
.then(res=>console.log(res.data))
.catch(e=>console.error(e.message));

// options 中的 type 会覆盖前面的 type
$webhook({ type: 'runjs' }, { type: 'shell', command: 'ls' })
.then(res=>console.log(res.data))
.catch(e=>console.error(e.message));
// 以上等同于
$webhook({ type: 'shell', command: 'ls' }).then(res=>console.log(res.data)).catch(e=>console.error(e.message));

// 当该脚本从 webhook 触发,且 type 为 runjs 时,会报错
// 比如在 test.js 中使用 $webhook('runjs', {fn: 'test.js'}) 再次调用 test.js
// 仅首次调用有效,之后的调用将不再继续执行
$webhook({ type: 'runjs', fn: 'test.js' })
.then(res=>console.log(res.data))
.catch(e=>console.error(e.message));

// 其他 $webhook 可执行类型及对应参数,请参考:https://github.com/elecV2/elecV2P-dei/blob/master/docs/09-webhook.md
```

## require 其他 nodejs module

``` JS example
// require 公共模块
const path = require('path')

// require 相对目录 js
const rob = require('./requireob')
const ob2 = require('./requireob2.js')

console.log(ob2, path.join(__dirname))
rob('hello elecV2P')

// #2 引用 node_modules 目录下的模块
const axios = require('axios')
axios.get('https://github.com/elecV2/elecV2P').then(res=>console.log(res.data))

// 删除 require 引用缓存 (v3.4.4)
let id = require.resolve('./requireob2.js')  // 获取缓存 id
delete require.cache[id]   // 删除对应 id module 的缓存

// 或者直接使用 require.clear
require.clear('./requireob2')   // 等同于 require.clear('./requireob2.js')
// 注意: require.clear 并不是 nodejs 中的标准函数,目前仅适用于 elecV2P

let rob2 = require('./requireob2.js')   // 再次引用时将会重新加载
```

- *require 函数在兼容其他软件脚本模式下默认不启用,可在文件开头使用 **// @grant   require** 强制开启*
- *require 有缓存,刚修改的脚本在其他模块引用时可能不会更新。可使用 require.clear 函数来清空相应缓存*
- *require 仅可引用 nodejs 原生脚本,如脚本中包含 elecV2P 等其他环境变量时(比如上面的 $store/$feed/$exec 等等)无法解析*

## $done - 返回 JS 执行结果

优先级: $done > JS 最后一条语句结果

``` JS example
let elecV2P = 'customize personal network'
elecV2P
// 返回字符串 'customize personal network'
// 即:当没有使用 $done 函数时,直接返回最后一条语句的执行结果

let elecV2P = 'customize personal network'
$done(123)
console.log(elecV2P)
elecV2P
// 返回结果 123,并且后面的 console.log 函数会正常执行

let elecV2P = 'customize personal network'
$done({ response: elecV2P })
// 返回对象 {response: 'customize personal network'}

let elecV2P = 'customize personal network'
let promise = Promise.resolve(elecV2P)
console.log(typeof promise)
promise
// 如果返回的是一个 Promise 函数,最后结果是 resolve 的值
// 即: customize personal network
```

- JS 默认 timeout 为 5000ms (5 秒),该值可在 webUI->SETTING 界面修改。0 表示无限制,没有超时时间
- JS 测试运行 和 webhook 运行 timeout 固定为 5000ms (以防长时间无返回结果而出现的错误),超时后后面的代码会继续执行(v3.4.0)
- 如果最后结果是异步函数,超时后代码会继续执行,如果是同步函数,会返回一个超时的错误信息

## $fend - efh 文件前后端数据交互(v3.5.5)

$fend 函数目前仅适用于 efh 文件。基本用法: $fend(key, data);
(关于 efh 文件的说明, 参考 https://github.com/elecV2/elecV2P-dei/blob/master/docs/08-logger&efss.md 中的相关部分)

``` JS
// efh 文件前端 script 部分
// 默认 timeout 为 5000ms,可在传输数据中使用 timeout 参数修改(v3.7.2)
// 比如 $fend('akey', {timeout: 1000, hello: 'other data'}).then(...)
$fend('skey', '传输给后台的数据')
.then(res=>res.text())
.then(alert)
.catch(e=>{
  console.error(e);
  alert(e.message);
});

// efh 文件后台 script 部分
$fend('skey', {
  statusCode: 200,
  header: { "Content-Type": "application/json;charset=utf-8" },
  body: {
    message: '后台 $fend 第二参数的标准格式为包含 statusCode/header/body 的 object',
    somenote: '但也可以是一个 string,甚至是一个函数',
    reqbody: $request.body
  }
})

// 清空 efh 文件运行产生的缓存(v3.5.5)
$fend.clear()
```

详细说明:

- 前端 $fend(key, data)
  - 本质是一个封装了 post 请求 fetch 函数,body: JSON.stringify({key, data})
  - 第二项参数(data)可省略

- 后台 $fend(key, data)
  - 第一项参数 key 需与前端相对应
  - 同一个 efh 文件可使用多对 $fend(建议只使用一对
  - 第二项参数 data 表示要返回给前端的数据
  - 当 data 不是 statusCode/header/body 标准返回 object 时,表示为 body 值。比如 $fend('akey', '简单的 body')
  - 当 data 是一个函数时,可接收前端发送的 data。比如 $fend('akey', (data)=>{console.log(data);return 'Got!'})
  - 函数返回值将作为最终值返回给前端
  - 如果 $done 在 $fend 之前执行,则 $fend 无效

几个简单的示例文件:
https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/elecV2P.efh
https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/simple.efh
https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/fendtest.efh

待优化:
- $fend data 函数比 $done 后执行的问题

## webUI 脚本管理部分说明

### 附带参数运行(v3.6.1)

可传递参数到执行脚本中,目前支持:

```
-env       // 增加临时变量。比如 tenv.js -env name=elecV2P cookie=我的Cookie
// 临时变量在脚本中通过 $env[变量名] 的方式获取。比如 $env.name, $env.cookie
-timeout   // 脚本超时时间。比如 test.js -timeout=0
-rename    // 重新命名脚本。比如 test.js -rename=t.js
-grant     // 脚本增加,具体参数见文档上面的 @grant 部分。比如 test.js -grant nodejs
-local     // 表示当本地脚本存在时不下载远程脚本(仅对远程脚本有效
```

以上所有参数可同时使用,比如 **https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/exam-js-env.js -local -rename=tenv.js -env name=你的名字 cookie=MYcookie -timeout=200 -grant nodejs**

### 新标签页运行(v3.6.2)

当运行 efh 文件时,可选择**新标签页运行**。运行后,将打开一个新的标签页,内容为 efh 文件的前端部分。

## 模拟网络请求 - mock 

![mock](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/mock.png)

### 本地 fetch / 服务器 axios

模拟网络请求发起的位置。可用于测试 网络请求相关设置 是否生效,当前网络是否通畅等。

### HEADERS

第一项选择内容为 **Content-Type** 的值,后面附加内容为 headers 的其他值(JSON 格式)。

### BODY

textarea 区域为 request body 对应值

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/05-rewrite.md
================================================
```
最近更新: 2021-10-16
适用版本: 3.5.0
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/05-rewrite.md
```

## 简述

在 elecV2P 中,REWRITE 规则是 RULES 规则特定项的简化版本,匹配效率较高,建议在可使用 REWRITE 的情况下,关闭 RULES 规则集。

## webUI 相关说明

![rewrite](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/rewritenote.png)

- REWRITE 规则的匹配对象为网络请求的 URL,如果是 https 请求,请在 MITM host 中添加对应的解析域名。
- *具体的匹配公式: `(new RegExp('匹配链接正则表达式')).test($request.url)`。*

v3.5.0 添加 **匹配阶段** 选项
- 网络请求前,用于修改网络请求体 request headers/body 等
- 数据返回前,用于修改获取到的内容 response headers/body 等

- 当规则对应重写方式为 (reject|reject-200|reject-dict|reject-json|reject-array|reject-img) 中的某个参数时,表示阻止该网络请求(直接返回相应内容)
- 当规则对应重写方式为脚本时,表示在通过该脚本修改该网络请求或返回内容
- 脚本编写参考说明文档 [04-JS.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md) 或 示例脚本 [0body.js](https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/0body.js)

- 订阅链接必须以 http 或 efss 开头,具体订阅内容参考下面的 **订阅内容格式** 部分
  - http: 表示订阅为远程地址,比如: https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/rewritesub.json
  - efss: 表示直接读取服务器上 EFSS 虚拟目录中的文件,比如 efss/rewritesub.json

- 删除订阅时并不会删除已添加的 MITMHOST 和 TASK
- 所有规则的更改在保存后才正式生效

## 源文件格式

REWRITE 规则列表保存于 **./script/Lists/rewrite.list**,实际格式为严格的 JSON 类型(不包含任何注释)。
*(参考: https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Lists/rewrite.list )*

``` JSON
{
  "rewritesub": {     // 订阅列表
    "eSubUuid": {
      "name": "elecV2P 重写订阅",
      "resource": "https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/rewritesub.json"
    }
  },
  "rewrite": {       // 规则列表
    "note": "elecV2P 重写规则",   // 关于规则列表的注释。可省略
    "list": [        // 具体规则
      {
        "match": "^https?://httpbin\\.org/get\\?rewrite=elecV2P",   // 网络请求 url 匹配
        "stage": "res",  // 匹配阶段。req: 网络请求前 res: 数据返回前。 v3.5.0 添加
        "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/0body.js",  // 匹配后使用的脚本文件
        "enable": true
      }
    ]
  }
}
```

*如非必要,请不要手动修改 list 源文件*

## 订阅内容格式

订阅内容同样为严格的 JSON 类型,不包含任何注释。参考: https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/rewritesub.json

``` JSON
{
  "name": "elecV2P 重写订阅",       // 订阅名称。可省略
  "resource": "https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/rewritesub.json",   // 该订阅的更新地址。可省略
  "type": "rewrite",                // 订阅类型,固定为 rewrite。用于区分 task 订阅,可省略
  "note": "关于该订阅的一些说明(可省略)。该订阅目前仅适用于 elecV2P,与其他软件并不兼容。更详细说明请查看: https://github.com/elecV2/elecV2P-dei/tree/master/docs/05-rewrite.md",
  "author": "https://t.me/elecV2",  // 制作者。可省略
  "bkcolor": "#2fa885",    // 该订阅背景颜色。当省略时,将随机生成。可使用 url(http://xxxx.jpg)
  "mitmhost": [      // 和重写规则相关的 mitmhost。可省略。
    "test.com", "httpbin.org"
  ],
  "list": [       // 重写规则列表
    {
      "match": "https:\\/\\/test\\.com\\/block",    // url 匹配正则表达式
      "stage": "req",    // 匹配阶段。req: 网络请求前 res: 数据返回前。 v3.5.0 添加
      "target": "reject-json",    // 阻止网络请求,并返回默认的 json 数据
      "enable": true
    },
    {
      "match": "https:\\/\\/httpbin\\.org",         // enable 可省略。如只添加不启用,则设置 enable: false
      "stage": "res",
      "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/exam-cheerio.js"
    }
  ],
  "task": {           // 同时添加定时任务(可省略)
    "type": "skip",   // 当任务列表中包含同名任务时,新任务的添加方式。skip: 跳过, addition: 新增, replace: 替换(默认,如省略)
    "list": [         // 任务列表,具体格式参考: https://github.com/elecV2/elecV2P-dei/tree/master/docs/06-task.md 订阅 list 相关部分
      {
        "name": "REWRITE 订阅添加的任务",
        "type": "cron",
        "time": "30 0 0 * * *",
        "job": {
          "type": "runjs",
          "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/test.js"
        }
      }
    ]
  }
}
```

*反引号"\" 为转义字符,实际保存/读取时会自动转义一次,请不要直接在订阅文件中进行复制,然后粘贴到 webUI 中。*

### 其他说明

- *REWRITE 列表的优先级高于 RULES 规则列表。*
- *规则订阅对其他软件的订阅格式有一定的兼容性,但并不保证完全适配。*
- *首次命中 https 请求时,系统会自动签发一张中间证书,可能需要稍长一点时间。*
- *推荐文章: [elecV2P 进阶使用之抓包及 COOKIE 获取](https://elecv2.github.io/#elecV2P%20%E8%BF%9B%E9%98%B6%E4%BD%BF%E7%94%A8%E4%B9%8B%E6%8A%93%E5%8C%85%E5%8F%8A%20COOKIE%20%E8%8E%B7%E5%8F%96)*

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/06-task.md
================================================
```
最近更新: 2022-02-08
适用版本: 3.6.0
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/06-task.md
```

![task](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/taskall.png)

### 时间格式

- 倒计时 30 999 3 2  (以空格分开的四个数字,后三项可省略)

|     30(秒)      |     999(次)    |      3(秒)         |       2(次)       
: --------------: | :--------------: | :------------------: | :------------------:
| 基础倒计时时间  |  重复次数(可选)| 增加随机时间(可选) | 增加随机重复次数(可选)  

*当重复次数大于等于 **999** 时,无限循环。*

示例: 400 8 10 3 ,表示倒计时40秒,随机10秒,所以具体倒计时时间位于 40-50 秒之间,重复运行 8-11 次

- cron 定时 

时间格式: * * * * * * (五/六位 cron 时间格式)

| * (0-59)   |  * (0-59)  |  * (0-23)  |  * (1-31)  |  * (1-12)  |  * (0-7)      
:----------: | :--------: | :--------: | :--------: | :--------: | :---------:
| 秒(可选) |    分      |    小时    |     日     |     月     |    星期


## 可执行任务类型

- 运行 JS: runjs
- Shell 指令: exec
- 开始任务: taskstart
- 停止任务: taskstop

### 运行 JS

支持本地 JS, 及远程 JS。 
本地 JS 文件位于 script/JSFile 目录,可在 webUI->JSMANAGE 中查看。设置定时任务时,直接复制文件名到任务栏对应框即可。

如使用远程 JS,则直接在任务栏对应框内输入以 **http 或 https** 开头的网络地址。远程 JS 默认更新时间为 86400 秒(一天),可在 webUI->SETTING 界面修改。超过此时间,则会先下载最新的 JS 文件,然后再执行。如果下载失败,会继续尝试执行本地 JS 文件。

*所以在执行需要特别准时的任务时,不建议使用远程 JS。或者提前手动更新一下,也可以设置一个稍微提前一点的定时任务提前下载好最新的 JS 文件,避免执行任务时先下载文件带来的延迟。*

*此模式下的脚本运行在 vm 虚拟环境中,如果想要以原生的 nodejs 环境运行脚本,请选择 **shell 指令** 模式,然后填写 node xxx.js。(关于两者的区别,参考 [04-JS.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md) 相关部分)*

定时任务运行 JS 还支持附带 env 临时变量, 使用 **-env** 关键字进行声明,然后在 JS 文件中使用 **$env[变量名]** 的方式进行读取。例如: **exam-js-env.js -env name=一个名字 cookie=acookiestring**

``` JS exam-js-env.js
// exam-js-env.js 文件内容
let name = $env.name || 'elecV2P'
console.log('hello', name)

if ($env.cookie) {
  console.log('a cookie from task env', $env.cookie)
}
// 如果变量值包含空格等特殊字符,先使用 encodeURI 进行编码
// 比如: command.js -env cmd=pm2%20ls
```

- v3.2.8 增加 -local 关键字,用于优先使用本地文件(如果存在),忽略默认更新时间间隔

具体使用:

| 运行 JS | https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/test.js -local

如果本地存在 test.js 文件,则直接运行,否则,下载远程文件后再运行

- v3.3.1 增加 -rename 关键字,用于重命名文件(支持重命名远程和本地文件)

| 运行 JS | notify.js -rename feed.js
| 运行 JS | https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/test.js -local -rename=t.js

*使用 -rename 参数运行每次都会重新写入文件内容,建议不要在运行频率较高的任务中使用*

- v3.4.5 增加 -grant 关键字,用于指定脚本增强功能。

可设置值 require/nodejs/quiet/sudo 等,多个值用英文竖线符(|)隔开。比如:

| 运行 JS | requirex.js -grant=nodejs
| 运行 JS | test.js -grant require|sudo

*grant 后面接一个等号(=)或空格( )*
*关于 grant 的功能,参考 [04-JS.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md) @grant 相关部分*

### Shell 指令

*Shell 指令的运行基于 nodejs 的 [child_process_exec](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) 模块*

timeout 默认为 60000ms(60秒)。如果要执行长时间命令,在 JS 中使用 $exec() 执行,将 timeout 设置为 0 (表示不设定超过时间),或其他数值。
**v3.4.1 更新: 可使用 -timeout=xx 参数来设置 timeout 时间。xx 为数字,单位 ms(省略不写)。比如: ls -timeout=2**

cwd 默认目录为 script/Shell
**v3.4.1 更新: 当使用 node 命令并且没有指定 cwd 时,默认 cwd 为 script/JSFile**

``` sh 示例命令
# 单条命令
ls
node -v
start https://github.com/elecV2/elecV2P
reboot

# 文件执行(先将相关文件放置到 script/Shell 目录下)
hello.sh
python3 -u test.py
binaryfile
# 以上文件会通过系统默认程序打开,请先安装好对应执行环境,以及注意文件的执行权限
# 文件可通过 EFSS 界面上传至相关目录

# 其他一些指令,仅供参考,谨慎使用(设置倒计时为 0,在需要时运行一次即可)
echo {} > task.list -cwd script/Lists  # 清空任务列表,重启后生效
pm2 restart elecV2P    # 重启 elecV2P
apk add git            # 使用 apk 安装一些常用包
git clone https://github.com/xxxx/xxxx -cwd script/JSFile    # 使用 git 命令 clone 远程库到 script/JSFile 目录
```

**v3.2.6 更新增加 -env/-cwd 变量** (*v2.3.4 版本到 v3.2.5版本,使用关键字是 -e/-c,为避免和其他命令冲突,v3.2.6 修改为 **-env/-cwd***)
可通过 **-cwd/-env** 关键字更改工作目录和环境变量,比如:

``` sh
sh hello.sh -env name=Polo
# 如果要传递比较复杂的环境变量,比如带有空格=-%*等特殊符合,建议在 JS 使用 $exec 函数来完成。具体参考说明文档 04-JS.md $exec 相关部分

ls -cwd script/JSFile

# 当使用 node 命令,且没有设置 cwd 时,默认 cwd 为 script/JSFile (v3.4.1)
node requirex.js
# 注意: 使用 node 命令执行的 JS,必须为原生 JS,即不包含 elecV2P 附加的 $axios/$feed/$store 等函数变量。
# 如果既想使用 node 命令执行 JS,又想使用 $feed/$store 等函数,可在 JS 中使用 $exec('node xxxx.js') 来执行。具体参考说明文档 04-JS.md $exec 相关部分
```

**v3.2.7 增加 -stdin 变量,用于延迟输入交互指令**

``` sh
askinput.py -stdin elecV2P%0Afine,%20thank%20you
# -stdin 后面的文字如果较复杂,应先使用 encodeURI 函数进行简单编码
# 默认延时时间为 2000ms (2秒),即在 2 秒后自动输入 stdin 后面的文字
# 更多说明参考: 04-JS.md $exec 相关部分
```

**v3.2.8 增加支持运行远程文件**

``` sh
python -u https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Shell/test.py

sh https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Shell/hello.sh

# 如果原来的命令中带有 http 链接,需使用 -http 进行转义
echo -http://127.0.0.1/efss/readme.md
# 也可以通知添加引号来避免这种问题
echo 'https://github.com/elecV2/elecV2P-dei/tree/master/docs/06-task.md'

# 假如没有转义,直接使用命令
echo http://127.0.0.1/efss/readme.md
# elecV2P 将会尝试先下载 http://127.0.0.1/efss/readme.md 文件到 script/Shell 目录,然后使用下载完成后的文件地址替换远程链接,所以最终输出结果可能是: /xxxx/xxxx/script/Shell/readme.md
# (v3.4.2 echo 无需转义,按原样输出)

# 部分常用网络命令已排除下载,比如: curl/wget/git/start/you-get/youtube-dl 开头命令
curl https://www.google.com/
```

- 远程文件默认下载目录为 **script/Shell**
- 远程文件执行时默认每次都会重新下载
- 如果远程文件下载失败将会尝试运行本地文件
- 可使用 \-local 关键字优先使用本地文件

``` sh
python3 -u https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Shell/test.py -local
# elecV2P 会检查本地 script/Shell 目录是否存在 test.py 文件,如果存在则直接运行,否则下载后再执行
```

- 如果原来的命令中带有 http 链接,需使用 -http 进行转义
- 以下常用命令已排除自动下载(即无需转义,可按原来的命令直接输入执行)
  - curl/wget/git/start  (v3.2.9)
  - you-get/youtube-dl   (v3.3.0)
  - aria2c/http/npm/yarn/ping/openssl/telnet/nc/echo    (v3.4.2)
  - *如果还有其他的常用网络相关命令,欢迎反馈添加*

## 保存任务列表

当点击**保存当前任务列表**后,当前任务列表,包含运行状态,以及订阅信息列表,会保存到 script/Lists/task.list 文件中,在重启 elecV2P 后,任务列表会自动从 task.list 中恢复。保存的任务基本格式为: 

``` JSON task.list
{
  "taskuuid": {                     // 定时任务 id, 在添加时会随机生成
    "name": "任务名称",
    "type": "schedule",             // 定时方式: cron 定时 / schedule 倒计时
    "time": "30 999 2 3",           // 定时时间,具体格式见上文说明
    "running": true,                // 任务运行状态
    "job": {                        // 具体执行任务
      "type": "runjs",              // 执行任务类型。 具体见上文 **可执行任务类型**
      "target": "test.js",          // 执行任务目标/指令
    }
  },
  "J8R0fbBN": {
    "name": "查看当前目录文件",
    "type": "cron",
    "time": "2 3 4 * * *",
    "running": false,
    "job": {
      "type": "exec",
      "target": "ls"
    },
    "group": "XjTmn1un"
  },
  "V2vw4B5D": {
    "name": "定时任务订阅",
    "type": "sub",                  // v3.2.1 增加订阅功能。以 type = sub 表示
    "job": {
      "type": "skip",               // 当订阅中存在同名任务时,选择合并方式: skip 跳过,replace: 替换, addition: 新增
      "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/tasksub.json",   // 远程订阅链接
    }
  },
  "XjTmn1un": {
    "name": "elecV2P 任务分组",
    "type": "group",                // v3.5.3 增加分组功能。以 type = group 表示
    "note": "定时任务默认分组",
    "collapse": false
  }
}
```

- **实际 list 文件为严格的 JSON 格式,不包含任何注释**
- **如非必要,请不要直接修改 list 源文件**

## 远程订阅(请勿添加不信任的订阅链接)

![tasksub](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/tasksub.png)

订阅内容格式为严格的 JSON 格式,不包含任何注释, 相关参数如下: 

``` JSON
{
  "name": "elecV2P 定时任务订阅",     // 订阅名称
  "note": "订阅描述,可省略。该订阅仅可用于 elecV2P, 与其他软件并不兼容。",
  "date": "2021-02-26 23:32:04",      // 订阅生成时间,可省略
  "author": "https://t.me/elecV2",    // 订阅制作者,可省略
  "resource": "https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/tasksub.json",  // 原始订阅链接,可省略
  "type": "none",                     // 订阅自动更新类型。可选参数 none-不自动更新, cron/schedule 定时。v3.6.0 新增
  "time": "2 3 5 * * *",              // 订阅自动更新时间。v3.6.0 新增自动订阅更新
  "list": [                           // 任务列表。任务格式参考上面的 task.list 部分
    {
      "name": "软更新",
      "type": "cron",
      "time": "30 18 23 * * *",
      "running": true,                // running 状态值可省略。仅当 running 值为 false 时,表示只添加该任务而不运行
      "job": {
        "type": "runjs",
        "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/softupdate.js"
      }
    }, {
      "name": "清空日志",            // 当 running 值省略时,添加任务也会自动执行
      "type": "cron",
      "time": "30 58 23 * * *",
      "job": {
        "type": "runjs",
        "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/deletelog.js"
      }
    }, {
      "name": "Python 安装(Docker下)",
      "type": "schedule",
      "time": "0",
      "running": false,               // 当 running 值为 false 时,任务只添加不运行
      "job": {
        "type": "runjs",
        "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/python-install.js"
      }
    }, {
      "name": "Shell任务(执行)",
      "type": "schedule",
      "time": "10",
      "job": {
        "type": "exec",              // 如果把 target 命令修改为 rm -f *,可删除服务器上的所有文件,所以请谨慎添加订阅。
        "target": "node -v"
      }
    }, {
      "id": "aUidxxxx",              // 3.4.7 增加可添加默认 id。添加默认 id 后将无视同名任务更新规则
      "name": "Shell任务(不执行)",   // 如果任务列表中已存在同 id 任务,将会替换原任务,否则将会新增
      "type": "cron",                // 请谨慎设置任务 id,避免覆盖掉其他任务(非必要情况不建议手动设置
      "time": "10 0 * * *",
      "running": false,
      "job": {
        "type": "exec",
        "target": "python -V"
      }
    }, {
      "name": "开始Shell定时任务",
      "type": "schedule",
      "time": "10",
      "job": {
        "type": "taskstart",        // 任务类型:开始其他任务。也支持使用 taskstop 暂停其他任务
        "target": "aUidxxxx",       // 其他任务的 id
      }
    }
  ]
}
```

- 如果在确认网络通畅的情况下(订阅链接可以直接通过浏览器访问),但在获取订阅内容时出现 **Network Error** 的错误提醒,可能是浏览器 CORS 导致的问题,请尝试直接下载订阅文件,然后上传到 EFSS 目录,或使用本地订阅导入
- **当订阅任务中包含类似 rm -f * 的 Shell 指令时,可能会删除服务器上的所有文件,请勿必清楚订阅任务后再进行添加,不要添加不信任的来源订阅**
- v3.6.0 新增订阅自动更新。自动更新时间以用户手动设置时间为准

### 本地订阅文件导入

- 在 EFSS 界面上传订阅文件,然后订阅链接直接填写: efss/tasksub文件名.json
- 或者直接使用当前服务器地址,例如: http://127.0.0.1/efss/tasksub.json

### 其他订阅格式转换

参考脚本 https://github.com/elecV2/elecV2P-dei/blob/master/examples/JSTEST/exam-tasksub.js

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/07-feed&notify.md
================================================
```
最近更新: 2022-08-04
适用版本: 3.6.9
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/07-feed&notify.md
```

## 通知方式

- FEED RSS 订阅
- IFTTT WEBHOOK
- BARK 通知
- 自定义通知
- 通知触发 JS

### Feed rss 订阅

地址为网页端口(默认为 80) + /feed
例如: **http://127.0.0.1/feed**

然后使用 rss 阅读软件直接订阅即可。

*局域网内的 RSS 只能在局域网内查看,有外网地址才能实现远程订阅*

### IFTTT webhook

IFTTT - If This Then That, 官方网站为:https://ifttt.com/

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/iftttnotify.png)

1. 在手机上下载 ifttt 软件,注册登录,用于接收实时通知。
2. 在 ifttt 中搜索 webhook,或访问 https://ifttt.com/maker_webhooks/ ,添加 webhook 服务
3. 在 ifttt 中新建一条规则,if **Webhook** than **Notifications**。 webhook 的 Event Name(事件名称)设置为: **elecV2P**

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/setiftttm.jpg)

4. 在 ifttt 的 webhook setting edit 中找到对应的 **key**, 然后把 key 填写到 webUI 后台管理页面的 setting 对应位置

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/setifttt.png)

* 如果想通过 telegram 接收信息,则设置: if **Webhook** than **telegram**
* 通过邮箱接收: if **Webhook** than **email**
* 像其他的短信通知,iOS Reminders,发送到网盘Drive/evernote/twiter/Alexa 等等,都可以通过类似的方式去实现

#### 测试设置是否成功

- 在 webUI->SETTING 通知相关设置点击测试按钮
- 或者在 webUI->JSMANAGE 页面的 JS 编辑框中复制以下代码:

``` JS
// 所有通知测试
$feed.push('elecV2P notification', '这是一条来自 elecV2P 的通知', 'http://192.168.1.101')

// IFTTT 通知单独测试
$feed.ifttt('IFTTT notification', '来自 elecV2P', 'https://github.com/elecV2/elecV2P')
```

然后点击测试运行,如果能收到通知,表示设置成功。如果没有收到,请查看程序的运行日志,对照上面的步骤检查设置是否正确。

### BARK 通知

iOS 端通知 APP,下载地址:https://apps.apple.com/app/bark-customed-notifications/id1403753865
Github 地址:https://github.com/Finb/Bark

下载 BAKR APP 获取 KEY,然后填写到 webUI->SETTING 界面中的 BARK KEY 位置。

* v2.9.3 更新支持 BARK 使用自定义服务器

开启方式: 在 BARK KEY 位置填写完整的服务器地址,比如 https://your.sever.app/youbarkkeylwoxxxxxxxkUP/

### 自定义通知

通过不同平台提供的 API 接口,实现实时通知。以 **SERVER 酱** 为例,根据官方(http://sc.ftqq.com/ )说明,获得通知 url 为类似: http://sc.ftqq.com/SCKEY.send 的链接地址,然后使用 POST 的方式提交数据,数据格式为:

```
{
  "text": `$title$`,
  "desp": `$body$可以随便加点自定义文字[链接]($url$)`
}
```

其中 **$title$**, **$body$**, **$url$** 三个字段分别表示原本通知的标题/主体和链接。

![](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/custnotify.png)

上图所示为通过自定义设置,实现SERVER 酱的通知。

如果是通知 GET 的请求方式进行通知,则直接在 URL 中使用这三个参数,例如:https://sc.ftqq.com/yourSCKEY.send?text=$title$

如果要使用其他的通知方式,请根据其他通知平台提供的 API 说明文档,自行进行设置。

例如使用 telegram bot 通知,通知链接:https://api.telegram.org/bot你的botapi/

选择 POST 方式,内容如下:

```
{
  "method": "sendMessage",
  "chat_id": 你的TG userid,
  "text": `$title$\n$body$\n$url$`
}
```

- *自定义通知数据最终提交格式,会自动进行判断。如果是 JSON 格式,会自动以 application/json 的方式提交。*
- *通常 API 都会有字符长度限制,比如 TG bot 的限制长度为 4096,在使用时可能需要注意。*
- *通知内容尽量使用反引号(\`) 包括*

### 通知触发 JS

可实现的功能:
  - 过滤通知
  - 自定义个性化通知
  - 其他 JS 能做的事

v3.4.5 更改:
  - 通知触发的 JS 默认以 nodejs 兼容模式运行
  - 增加临时环境变量 $env.title/body/url

``` JS
// 通过临时环境变量 $env.title/$env.body/$env.url 分别获取通知内容
console.log('title:', $env.title, 'body:', $env.body, 'url:', $env.url)

if ($env.title) {
  console.log('通知触发的 JS', $env.title)
}

// 可以过滤通知或自定义其他通知方式
if (/important/.test($env.title)) {
  mynotify($env.title, $env.body, $env.url)
}

function mynotify(title, body, url) {
  // 根据个人需求填写
  console.log('自定义其他通知方式', '标题:', title, '内容:', body, '附加链接:', url)
}
```

*具体写法可参考: https://github.com/elecV2/elecV2P/blob/master/script/JSFile/notify.js*

**因为在 JS 中可通过 $feed.push 发送通知,通知又可以触发 JS,为避免循环调用,在通知触发的 JS 中 $feed.push 函数不可用,其他通知函数($feed.ifttt, $feed.bark, $feed.cust)可正常使用,但不会触发 JS。**

## 默认通知内容

- 任务开始/暂停/删除
- 倒计时任务完成
- JS 运行设定次数(默认 50)

*如果在非手动重启的情况下收到大量默认通知,可能是因为某些脚本的运行导致 elecV2P 重启,请尝试根据 errors.log 和相关脚本的日志,定位并解决问题*

## 在 JS 调用通知模块

**请提前在 webUI->SETTING/设置相关 界面填写好通知参数**

### 关键字:$feed

**$feed.push(title, description, url)**

- 添加一个 rss item 及通知
- url 可省略,如省略 title/description 内容,将自动补充默认字符,以防部分通知软件因为空数据而导致通知失败的情况
- 如 title 省略,将默认补充为: **elecV2P 通知**
- 如 description 省略,将默认补充为: **a empty message\n没有任何通知内容**

``` JS example
$feed.push('elecV2P notification', '这是一条来自 elecV2P 的通知', 'https://github.com/elecV2/elecV2P')

// 发送一条 IFTTT 通知。(先设置好 ifttt webhook key)
$feed.ifttt('title', 'description', 'https://github.com/elecV2/elecV2P-dei')   
// 发送一条 BARK 通知
$feed.bark('Bark notification', 'a bark notification', 'https://t.me/elecV2')
// 发送一条自定义通知
$feed.cust('elecV2P customize notification', `一条自定义通知。\na customize notification`, 'https://t.me/elecV2G')

// 【已移除】 在通知关闭的情况下,在 title 开头添加 $enable$ 强制发送 (v3.2.8 添加功能)
// v3.6.9 $enable$ 强制发送功能已移除
$feed.bark('$enable$elecV2P 强制通知', '通过在 title 开头添加 $enable$ 强制发送的通知', 'https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/overview.png')
```

### 其他说明

- 当通知标题(title)中含有 **test** 关键字时,自动跳过,不添加通知内容。(方便调试)
- 当通知主体(description)内容长度超过一定数值(默认 1200)时,会自动进行分段通知
  - 默认 feed 通知不限制字符长度,不分段
  - 单独调用($feed.ifttt/$feed.bark/$feed.cust)时也不分段通知
  - 只有默认通知和使用 **$feed.push**,在字符超过设定值时才会分段发送。该设定值可在 webUI->SETTING 界面修改,0 表示始终不分段

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/08-logger&efss.md
================================================
```
最近更新: 2022-10-03
适用版本: 3.7.2
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/08-logger&efss.md
```

## LOG 日志

物理存储位置:项目目录/logs。 ./logs
网络访问地址:webUI 端口/logs。 比如: http://127.0.0.1/logs

日志分类:

- 错误日志 errors.log
- shell 命令执行日志 funcExec.log
- 其他未处理类日志 elecV2Proc.log
- JAVASCRIPT 运行日志 xxx.js.log
- 定时任务 shell 指令日志 任务名.task.log
- 访问日志 access.log(v3.4.6 添加

支持多级目录。 比如在当前 logs 文件夹下有一个目录 backup, backup 下有一个日志文件 test.js.log,那么对应查看 url 为: http://127.0.0.1/logs/backup/test.js.log 。如果直接访问 http://127.0.0.1/logs/backup 将出列出该文件夹下的所有日志文件。

注意事项:
- 最多只显示 1000 个日志文件
- 未显示文件可以直接通过文件名访问。比如: http://127.0.0.1/logs/具体的日志名称.log
- 多级 JS 运行并不会生成多级日志。比如 test/123/45.js 脚本对应的日志名为 test-123-45.js.log

### errors.log

程序运行时所有的错误日志。如果程序意外崩溃/重启,可在此处查看错误原因。

### access.log

访问日志记录的是 websocket 的连接时间,可能并不是很准确,更详细的日志可在后台进行查看。

### funcExec.log

所有执行过的 Shell 指令及日志。

### 其他脚本日志

JS 脚本中 console 函数输出的内容。每个脚本单独一个文件,命名格式为: filename.js.log。
子目录中的 JS 日志文件名为,目录-文件名.js.log,比如: test-a.js.log

在 JSMANAGE 界面进行测试运行的脚本,日志命名格式为: filename-test.js.log。

*如果有太多日志文件,可直接手动删除,并不影响使用。默认 JS 文件中包含 deletelog.js,可设置一个定时任务进行自动清除。*

## 清空日志

手动删除:
cd logs
rm -f *  (该指令会删除当前目录下所有文件,请不在其他目录随意使用)

清除单个日志文件:
rm -f 日志文件名

或者在脚本中使用 **console.clear()** 函数,清空该脚本的相关日志。

自动删除:
使用自带的 deletelog.js 配合定时任务进行删除。
在 webUI -> TASK 界面添加一个定时任务,名称随意,时间自行选择,任务选择执行 JS,后面填写 deletelog.js。

例如,设置每天23点59分清除一下日志文件:

清空日志 | cron定时 | 59 23 * * * | 运行 JS | https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/deletelog.js

## EFSS - elecV2P file storage system

elecV2P 文件管理系统

目的:用于比较大的文件存储和读取,比如图片文件/视频文件。

功能:
- 共享任一文件夹,方便局域网内文件传输
- 下载远程网络文件至任意目录
- 配合 $exec/手动安装 aria2, 实现下载磁力/种子文件等(测试中

### 访问路径 - /efss

例如:http://127.0.0.1/efss

*无论物理目录如何改变,网络访问地址不变*

### efss 目录设置

默认目录:当前工作路径/efss

可手动设置为其他任意目录

**./** - 相对目录。相对当前工作路径。 例如:./script/Shell, ./logs, ./script/JSFile, ./rootCA 等等

**/**  - 绝对目录。 例如: /etc, /usr/local/html, D:/Video 等等

**注意:**

- 如果目录中包含大量文件,例如直接设置为根目录 **/**,在引用时会使用大量资源(CPU/内存)去建立索引,请合理设置 efss 目录*
- 默认最大显示文件数为 600(可更改
- 没有显示的文件可以直接通过文件名访问
- 下载远程文件时,可使用 -rename 重命名文件。比如: https://x.com/l.json -rename=h.json

## EFSS favend

EFSS favorite&backend,用于快速打开/查看某个目录的文件(favorite),以及将脚本作为 backend 返回执行结果。

可以简单理解为 favorite 返回系统静态文件,backend 返回脚本动态生成的“文件”。

![favend](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/favend.png)

其中关键字表示 favend 访问路径,比如: **http://127.0.0.1/efss/test**, **http://127.0.0.1/efss/cloudbk**

**favend 的优先级高于 EFSS 目录中的文件**

比如: 假如 EFSS 默认目录中有一个文件 **mytest**, 同时存在某个 favend 的关键字也为 **mytest**,那么会返回 favend 中的对应结果。(**请尽可能的避免此种情况**)

### favend - favorite 收藏目录

列出某个文件夹下的所有文件。

默认最大返回文件数 **1000**,可在 url 中使用 max 参数来进行更改,比如: **http://127.0.0.1/efss/logs?max=8**

默认是否显示 dot(.) 开头文件共用 EFSS 中相关设置,也可以在 url 中使用参数 dotfiles 来设置,比如: **http://127.0.0.1/efss/logs?dotfiles=allow** (除 dotfiles=deny 外,其他任意值都表示 allow 允许)

v3.7.1 后将在收藏目录下自动寻找 "index" 文件,默认为 **index.html**(不包含子目录)。比如收藏目录 x/blog 下存在文件 index.html,将直接显示 index.html 页面。可在 url 中使用 index 参数修改待查找的 index 文件,比如 **http://127.0.0.1/efss/blog/?index=main.html** (blog 后面须有斜杠/),将会显示 main.html 页面(如果存在的话)。如果 index 文件不存在,将像之前一样返回所有文件列表。

作用:

- 给 task/rewrite 设置专门的订阅目录
- 搭建临时静态网站
- 小型网盘资源分享
- 记录收藏常用目录

### favend - backend 运行脚本

作为 backend 运行的脚本默认 timeout 为 5000ms,也可以在 url 中使用参数 timeout 来修改,比如: **http://127.0.0.1/efss/body?timeout=20000**

该模式下的 JS 包含 **$request** 默认变量,且应该返回如下 object:

``` JS
console.log($request)   // 查看默认变量 $request 内容(该模式下的 console.log 内容前端不可见,只能在后台看到
// $request.method, $request.protocol, $request.url, $request.hostname, $request.path
// $request.headers<object>, $request.body<string>

// backend 特有属性 $env.key 表示访问该 backend 的关键字,$env.name 表示该 backend 名称
console.log($env.key, $env.name, __version, 'cookieKEY:', $store.get('cookieKEY'))   // 其他默认变量/函数也可直接调用

// 最终网页返回结果
$done({
  statusCode: 200,    // 网页状态码,其他状态码也可以。比如: 404, 301, 502 等。可省略,默认为: 200
  headers: {          // 网页 response.headers 相关信息。可省略,默认为: {'Content-Type': 'text/html;charset=utf-8'}
    'Content-Type': 'application/json;charset=utf-8'
  },
  body: {             // 网页内容。这里面的内容会直接显示到网页中
    elecV2P: 'hello favend',
    cookieKEY: $store.get('cookieKEY'),
    request: $request,
  }
})

// === $done({ response: { statusCode, headers, body } })

// 当$done 是其他结果时,比如: $done('hello favend')
// elecV2P 会把最终结果当作 body 输出,其他项使用默认参数
```

favend 也接受 post/put 等请求。支持在 body 中添加临时环境变量(env)参数,比如:

``` JS
// 需提前在 EFSS 界面中设置好 favend 参数 envtest
// 然后使用浏览器的开发者工具发送如下请求(也可以在 webUI->JSMANAGE 中模拟网络请求
fetch('http://127.0.0.1/efss/envtest', {
  method: 'put',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "timeout": 2000,
    "env": {
      "param": "传递一个变量"
    }
  })
}).then(res=>res.text()).then(s=>console.log(s)).catch(e=>console.error(e))

// envtest 对应运行的 JS 如下:
console.log('临时环境变量', $env.param, 'favend key', $env.key, 'favend name', $env.name)

// 注意: $request.body 为字符串类型,$env 为 object 类型
console.log('request body type:', typeof $request.body, '$env type', typeof $env)

$done({
  body: {
    'param': `通过 ${ $request.method } 获取到的 param: ${ $env.param }`,
    'request': $request
  }
})
```

### elecV2P favend html(.efh)  (v3.5.4 新增文件格式)

一个同时包含前后端运行代码的 html 扩展格式,也可以说是一个文件协议或标准。基础结构如下:

``` HTML
<div>原来的 html 格式/标签/内容</div>
<script type="text/javascript">
  console.log('原 html 页面中的 script 标签')
</script>
<!-- 上面为原 html 页面,下面为扩展部分 -->
<!-- <script type="text/javascript" runon="elecV2P"> v3.6.7 版本之前的写法 -->
<script favend>
  console.log('efh 文件的扩展部分')
</script>
```

执行过程/基本原理:
- 首次执行 efh 文件时,将文件分离为**前端和后台**部分,并进行缓存
- 然后当收到 POST 请求时,执行**后台部分**代码,返回执行结果
- 当收到其他请求时,直接返回**前端 HTML** 页面

优点:
- 前后端代码同一页面,方便开发者统一管理
- 标签内代码高亮(最初要解决的问题
- 沿用 html 语法,没有额外的学习成本

#### efh 实战测试

测试文件: https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/elecV2P.efh
适用版本: v3.5.5

在 EFSS 页面 favend 相关设置中添加新的规则,设置好名称和关键字,**类型** 选择 **运行脚本**, 目标填写 efh 文件远程或本地地址(*本地文件可在 webUI->JSMANAGE 脚本管理界面推送/上传/编辑*),然后点击保存(ctrl+s),最后在操作栏中点击运行。

#### efh 格式文件说明

``` HTML
<h3>一个简单的 efh 格式示例文件</h3>
<div><label>请求后台数据测试</label><button onclick="dataFetch()">获取</button></div>
<input type="text" name="data" class="data" placeholder="data">

<script type="text/javascript">
  // v3.7.2 增加 $ 简易选择器函数,示例:
  // let data = $('.data').value;   // 等价于 document.querySelector('.data').value
  // let div  = $('div', 'all');    // 等价于 document.querySelectorAll('div')

  // $fend 默认函数用于前后端数据交互(本质为一个 fetch 的 post 请求
  function dataFetch() {
    $fend('data').then(res=>res.text())
    .then(res=>{
      console.log(res)
      alert(res)
    })
    .catch(e=>{
      console.error(e)
      alert('error: ' + e.message)
    })
  }
</script>
<!-- 前端部分可以使用多个 script 标签
使用 src 引入任意远程文件,比如:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

(v3.7.1) 如需引入 elecV2P 服务器上的本地脚本,加上 /script 前缀,比如:
<script src="/script/webhook.js"></script>
支持多级目录,比如:
<script src="/script/test/webhook.js"></script>
前端 src 暂不支持使用相对目录,比如 src='./webhook.js' ,会提示脚本不存在
-->

<!-- 后台代码仅能使用一个 script 标签,如有其他多的标签将被当作前端代码处理
使用 runon="elecV2P" 属性来表示此部分脚本是运行在后台的代码
v3.6.7 之后可简写为 <script favend>  -->
<script type="text/javascript" runon="elecV2P">
  // 后台 $fend 第一参数需与前端对应,第二参数为返回给前端的数据
  $fend('data', {
    hello: 'elecV2P favend',
    data: $store.get('cookieKEY'),
    reqbody: $request.body
  })
  $done('no $fend match');
</script>
<!-- 后台 script 标签同样支持 src 属性,比如:
<script favend src="https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/favend.js"></script>

后台 src 在引入 elecV2P 服务器上的本地脚本时,无需添加 /script 前缀,支持相对、绝对目录,比如:
<script favend src="favend.js"></script>
<script favend src="./0body.js"></script>
// 相对目录,表示的是相对于当前 efh 文件
<script favend src="/webhook.js"></script>
// 绝对目录,表示的是服务器上的脚本根目录
```

其他说明:
- 无后台代码时直接返回前端 html 内容
- 直接运行 efh 脚本时,返回前端 html 内容
- 远程 efh 更新间隔和远程 JS 更新间隔同步
- v3.5.5 增加默认 $fend 函数用于前后端数据交互(具体参考 [04-JS.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md) $fend 相关部分
- 其他 efh 示例脚本:https://github.com/elecV2/elecV2P-dei/tree/master/examples/JSTEST/efh
- 另一篇相关说明文章:https://elecv2.github.io/#efh:一种简单的%20html%20语法扩展结构

待优化项:
- 其他类型数据 arrayBuffer/stream 等
- $fend 后台无匹配时返回结果
- 默认前端内置脚本可选择是否使用远程链接

优化完成:
- 前后台数据的持续交互($ws.sse
- efh 前端调用本地 JS/CSS/图片 等(part done
- $fend key/路由 配对优化
- runJS 直接运行 efh 文件
- 前后台更好/优雅的传输数据($fend(done
- 缓存清理(done) $fend.clear();

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/09-webhook.md
================================================
```
最近更新: 2022-10-30
适用版本: 3.7.4
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/09-webhook.md
```

webhook 用于使用一个网络请求来调用 elecV2P 的部分功能

## 可调用功能列表

- 获取脚本列表/运行脚本
- 获取/删除 日志
- 获取服务器相关信息
- 获取定时任务信息
- 开始/暂停 定时任务
- 添加/保存 定时任务
- 远程下载文件到 EFSS
- 执行 shell 指令
- 查看/修改 store/cookie(v3.3.3)
- 脚本文件获取/新增(v3.4.0)
- IP 限制 黑白名单更新(v3.4.0)
- 打开/关闭代理端口(v3.4.8)
- 全局 CORS 设置(v3.5.4-v3.7.3, v3.7.4 后移除)
- 使用根证书签发任意域名证书(v3.5.8)

## 使用

首先在 webUI-> SETTING/设置相关中获取 WEBHOOK TOKEN,然后可通过 GET/PUT/POST 三种请求方式触发相关功能。

下面以几个简单的例子进行说明。

### 运行 JS

GET 方式通过 url 传递相关参数,比如运行 JS,触发的请求链接为:

**http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=runjs&fn=webhook.js**

PUT 或者 POST 以 JSON 的方式传递相关参数, 以在浏览器在使用 fetch 函数为例

``` JS webhook
fetch('http://192.168.1.102:12521/webhook', {
  method: 'put',     // or post
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'runjs',
    fn: 'webhook.js'
  })
}).then(res=>res.text()).then(s=>console.log(s))

fetch('/webhook', {   // 本地服务器可直接用 /webhook
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'runjs',
    fn: 'https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/exam-js-env.js',        // 支持远程 JS
    env: {   // (v3.4.5 支持添加临时环境变量)
      name: 'webhook',
      cookie: '来自 webhook 的临时环境变量'
    },
    grant: 'nodejs',   // (v3.4.5 增加支持 grant,多个 grant 用英文竖线符(|)隔开。具体功能参考 04-JS.md @grant 相关部分)
  })
}).then(res=>res.text()).then(s=>console.log(s))
```

- 如果是远程脚本, 会强制下载脚本文件并保存
- 支持使用 rename 参数,修改远程脚本下载后的文件名

## body/query 参数说明

|  type     |   target 目标  |    功能         |        传递参数
| :-------: | -------------- | --------------- | --------------------
| runjs     | fn=webhook.js  | 运行脚本        |  &type=runjs&fn=webhook.js
| status    | 无 ---         | 服务器运行状态  |  &type=status
| task      | 无 ---         | 获取任务列表    |  &type=task
| tasksave  | 无 ---         | 保存任务列表    |  &type=tasksave
| taskinfo  | tid=all or tid | 获取任务信息    |  &type=taskinfo&tid=all
| taskstart | tid=xxtid      | 开始定时任务    |  &type=taskstart&tid=xxxowoxx
| taskstop  | tid=xxtid      | 暂停定时任务    |  &type=taskstop&tid=xxxowoxx
| getlog    | fn=xxxxxxx.log | 查看日志文件    |  &type=getlog&fn=xxxxxxx.log
| deletelog | fn=file.js.log | 删除日志文件    |  &type=deletelog&fn=file.js.log
| taskadd   | task: {}       | 添加定时任务    |  { type: 'taskadd', task: {}, options: {} }
| download  | url=http://xxx | 下载文件到EFSS  |  &type=download&url=https://rawxxxx
| shell     | command=ls     | 执行 shell 指令 |  &type=shell&command=node%20-v
| info      | debug=1  可选  | 查看服务器信息  |  &type=info or &type=info&debug=true
| jslist    | 无 ---         | 获取脚本列表    |  &type=jslist
| store     | key=cookieKEY  | 获取 cookie 信息|  &type=store&key=cookieKEY
| deljs     | fn=webhook.js  | 删除脚本文件    |  &type=deljs&fn=webhook.js
| jsfile    | fn=test.js     | 获取脚本内容    |  &type=jsfile&fn=test.js
| security  | op=put&enable. | 后台 IP 限制修改|  &type=security
| proxyport | op=open/close  | 打开/关闭代理   |  &type=proxyport&op=open
| blackreset| 无 ---         | 重置非法IP 记录 |  &type=blackreset
| newcrt    | hostname=xx.xx | 签发域名证书    |  &type=newcrt&hostname=myhost.com

- **每次请求时注意带上 token**
- **如果使用 PUT/POST 方式,body 应为对应的 JSON 格式**
- **command 指令需先使用 encodeURI 进行编码**
- **shell 执行默认 timeout 为 5000ms(以防出现服务器长时间无响应的问题)**

- (v3.7.1 更新) 当 type 参数缺省,或对应参数不在列表中时,可使用脚本处理 payload(即 request body)。需在 webUI->SETTING 设置界面启用 WEBHOOK SCRIPT。此种方式触发的脚本,通过 **$env.payload** 变量来获取除 token 以外的所有 body 参数

- (具体使用方法,参考下面的相关示例)

## 直接 GET 请求

```
# 获取内存使用信息
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=status

# 获取当前所有任务
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=taskinfo&tid=all

# 远程离线下载文件到 EFSS 虚拟目录
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=download&url=https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/overview.png

## 自定义下载文件保存目录和名称(v3.4.0)
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=download&url=https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/evui-dou.js&folder=script/JSFile&name=edou.js

# 列出 script/Shell 目录下的文件
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=shell&command=ls%20-cwd%20script/Shell

## shell 使用 cwd 和 timeout 参数
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=shell&command=ls&cwd=script/JSFile&timeout=2000

# 获取 elecV2P 及服务器相关信息
http://192.168.1.102:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=info&debug=true

# 查看 store/cookie 信息
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=store&op=all          # 获取 cookie 列表
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=store&key=cookieKEY   # 获取某个 KEY 对应值
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=store&op=put&key=cookieKEY&value=webhookgetvalue   # 添加一个 cookie

# 后台 IP 限制查看/修改
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=security              # 查看当前 SECURITY 设置

## 修改后台 IP 限制。关键参数 op=put,其他修改参数 enable, blacklist, whitelist 可只设置一项,list 中多个数据用逗号(,)分开。
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=security&op=put&enable=true&blacklist=*&whitelist=127.0.0.1,192.168.1.1

## 关闭仅开放 webhook 接口选项(v3.6.4)
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=security&op=put&webhook_only=false

# 删除默认脚本文件夹下的所有非脚本文件(v3.4.2)
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=deljs&op=clear

# 重置非法访问的 IP 记录(v3.5.5)
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=blackreset

# 使用根证书签发任意域名证书(v3.5.8)
# 生成的域名证书位于 当前项目/rootCA 目录下
http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=newcrt&hostname=v2host.io
```

## 使用 PUT/POST 方法

``` JS
// 在 webUI 后台管理页面打开浏览器调试工具,输入以下代码,可不输入服务器地址
// # 添加定时任务 2.4.6 更新
// task 格式参考: https://github.com/elecV2/elecV2P-dei/tree/master/docs/06-task.md

fetch('/webhook', {
  method: 'put',     // or post
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'taskadd',
    task: {
      name: '新的任务-exam',
      type: 'cron',
      job: {
        type: 'runjs',
        target: 'https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js',
      },
      time: '10 8 8 * * *',
      running: false        // 是否自动执行添加的任务
    }
  })
}).then(res=>res.text()).then(s=>console.log(s)).catch(e=>console.log(e))

// v3.4.2 增加支持批量添加,及 options 参数
fetch('/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'taskadd',
    task: [         // 以数组的格式批量添加任务
      {
        name: '新的任务-exam',
        type: 'cron',
        job: {
          type: 'runjs',
          target: 'https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js',
        },
        time: '10 8 8 * * *'
      },
      {
        name: 'apk安装命令',
        type: 'schedule',
        time: '0',
        running: false,
        job: {
          type: 'exec',
          target: 'apk add git'
        }
      }
    ],
    options: {              // v3.4.2 增加。可省略
      type: 'replace',      // 当任务列表中存在同名任务时的更新方式。replace: 替换原有任务,skip: 跳过添加新任务,addition: 新增任务
    }
  })
}).then(res=>res.text()).then(s=>console.log(s)).catch(e=>console.log(e))

// v3.4.2 同时添加 taskstart/taskstop 的批量处理
fetch('/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'taskstart',   // taskstart: 开始任务 taskstop: 停止任务
    tid: ['Wnj8rMaj', 'nPfq5Td3', 'cKUq3ViR'],     // 对应任务的 id
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// # 增加/修改 store/cookie (v3.3.3)
fetch('/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'store',
    op: 'put',
    key: 'acookiehook',   // cookie key 关键字
    value: {              // cookie 保存内容。可为 string/number 等其他数据类型
      hello: 'elecV2P'
    },
    options: {            // options 可省略
      type: 'object',     // 指定 value 保存类型,可省略(如省略将根据 value 的类型进行自动判断
      belong: 'webhook.js',        // 指定该 cookie 归属脚本
      note: '一个从 webhook 添加测试 cookie',  // 给 cookie 添加简单备注
    }
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// 在服务器中新增一个脚本文件(v3.4.0)
fetch('http://192.168.1.3/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'jsfile',
    op: 'put',
    fn: 'awbnew.js',      //脚本文件名
    rawcode: `//脚本文件内容
console.log('一个通过 webhook 新添加的文件')`
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// 更改可访问后台管理页面的 IP(v3.4.0)
// enable, blacklist, whitelist 可只设置其他一项,其他项会自动保持原有参数
fetch('http://172.20.10.1/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'security',
    op: 'put',
    enable: false,
    blacklist: ['*'],
    whitelist: ['127.0.0.1', '172.20.10.1']
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// 批量删除脚本(v3.4.2)
fetch('/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'deletejs',   // 同等于: deljs === jsdel === jsdelete
    // op: 'clear',     // 启用此项,表示删除默认脚本文件夹下的所有非脚本文件
    fn: ['test/starturl.js', 'test/restart.js', '0body.js', 'test.js']   // 使用数组表示多个要删除的脚本文件
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// 打开代理端口(默认为 8001
fetch('/webhook', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token: 'a8c259b2-67fe-D-7bfdf1f55cb3',
    type: 'proxyport',
    op: 'open',     // 仅当该值为 open 时,表示打开。
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// type 参数缺省,或对应参数不在列表中时,可使用脚本来处理 payload (v3.7.1)
fetch('/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3', {
  method: 'post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    // type: 'unknow', // type 缺省
    version: 14,       // payload(即 body 内容) 为其他未知值
    some: 'value',
  })
}).then(res=>res.text()).then(res=>console.log(res)).catch(e=>console.log(e))

// 在 webUI SETTING 设置好 WEBHOOK SCRIPT 相关内容后,可在对应脚本中使用 $env.payload 来获取任意网络请求的 payload
// 假如设置对应的脚本为 webhook.js ,webhook.js 的内容如下:
// console.log('获取到的 payload', $env.payload);$done($env.payload);
```

- 假如 elecV2P 可远程访问,可以使用使用其他任意程序发送网络请求进行调用
- webhook 可配合 **telegram bot** 或 **快捷指令** 等其他工具使用,方便快速调用 elecV2P 相关功能
- 通过 webhook 提供的 API,可以自行设计其他 UI 界面,实现与 elecV2P 交互
- v3.5.8 在脚本中增加函数 $webhook(type, options) ,详见 https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md 相关说明

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/10-config.md
================================================
```
最近更新: 2022-11-06
适用版本: 3.7.5
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/10-config.md
```

## 配置文件说明

elecV2P 配置文件默认保存目录为 **./script/Lists/config.json**。

**请尽量在 webUI->SETTING/设置相关 界面进行修改,而不是手动编辑。**

## 内容

*实际配置文件为严格的 JSON 格式,不包含任何注释。以下注释仅为说明,非注释版本查看 [config.json 示例文件](https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Lists/config.json)*

``` JSON
{
  "anyproxy": {                // anyproxy 相关设置。用于 MITM
    "enable": false,           // 是否启用。默认 false 关闭
    "port": 8001,              // anyproxy 代理端口
    "webPort": 8002            // anyproxy 网络请求查看端口
  },
  "CONFIG_FEED": {             // 通知相关设置
    "enable": true,            // 是否开启默认通知
    "rss": {
      "enable": true,          // 是否将通知输出为 feed/rss
      "homepage": ""           // feed/rss 主页。同最外层 homepage 参数
    },
    "iftttid": {
      "enable": false,         // 是否启用 IFTTT 通知
      "key": ""                // IFTTT 对应 key 值
    },
    "barkkey": {
      "enable": false,         // 是否启用 BARK 通知
      "key": ""
    },
    "custnotify": {
      "enable": false,         // 是否启用自定义通知
      "url": "",               // 自定义通知 URL
      "type": "GET",           // 自定义通知内容发送方式
      "data": ""               // 自定义通知内容
    },
    "runjs": {
      "enable": false,         // 通知时触发脚本,即通过自定义脚本发送通知
      "list": "notify.js"      // 通知时运行脚本
    },
    "merge": {
      "enable": true,          // 是否合并默认通知
      "gaptime": 60,           // 合并该时间段内通知。单位:秒
      "number": 10,            // 合并通知条数
      "andor": false           // 时间段和通知条数合并逻辑。true: 同时满足,false: 满足任一
    },
    "maxbLength": 1200,        // 最大通知内容长度。超过后将分段发送
    "webmessage": {
      "enable": true           // 是否在 webUI 前端显示通知
    }
  },
  "CONFIG_RUNJS": {            // 脚本运行相关设置
    "timeout": 5000,           // 脚本运行时间。单位:毫秒 0 表示不设定超时时间
    "intervals": 86400,        // 远程脚本最低更新时间间隔,单位:秒。 默认:86400(一天)。0 表示有则不更新
    "numtofeed": 50,           // 每运行 { numtofeed } 次脚本, 发送一个默认通知。0 表示不通知
    "jslogfile": true,         // 是否保存脚本运行日志
    "eaxioslog": false,        // 是否保存网络请求 url 到日志中
    "proxy": true,             // 是否应用网络请求相关设置中的代理(如有)
    "white": {
      "enable": false,         // 是否启用白名单脚本。放行脚本内所有网络请求
      "list": ["softupdate.js"]
    }
  },
  "CONFIG_Axios": {            // 网络请求相关设置
    "proxy": {
      "enable": false,         // 是否使用 http 代理
      "host": "",              // 代理服务器
      "port": 8001             // 代理端口
    },
    "timeout": 5000,           // 网络请求超时时间。单位:毫秒
    "uagent": "iPhone",        // 默认 User-Agent,相关列表位于 script/Lists/useragent.list
    "block": {
      "enable": false,         // 是否阻止部分网络请求。匹配方式 new RegExp('regexp').test(url)
      "regexp": ""
    },
    "only": {
      "enable": false,         // 当前启用时,表示仅允许符合该规则的 url 通过
      "regexp": ""
    },
    "reject_unauthorized": true       // process.env['NODE_TLS_REJECT_UNAUTHORIZED'] 设置。仅当为 false 时,对应值为 0。v3.7.4 增加
  },
  "eapp": {                    // EAPP 相关设置。详见 https://github.com/elecV2/elecV2P-dei/blob/master/docs/dev_note/webUI%20首页快捷运行程序%20eapp.md
    "enable": true,            // EAPP 是否在首页显示
    "logo_type": 1,            // EAPP 默认图标风格
    "apps": [{                 // EAPP 列表
      "name": "EAPP 名称",
      "type": "js",            // EAPP 类型。共 js|efh|shell|url|eval 五种
      "target": "efss/efh",    // EAPP 最终执行内容
      "hash": "xxxxx",         // EAPP hash 值,自动生成,手动设置无效
    }, {
      "name": "PM2LS",
      "type": "shell",
      "target": "pm2 ls",
      "run": "auto",           // 首次加载时运行方式。仅当为 auto 时表示自动运行一次(v3.7.3 增加
      "note": "显示 PM2 信息"  // EAPP 备注信息(v3.7.3 增加
    }]
  },
  "efss": {                    // EFSS 相关设置
    "enable": true,            // 是否启用
    "directory": "./efss",     // EFSS 对应文件夹
    "dotshow": {
      "enable": false          // 是否显示以点(.) 开头的文件
    },
    "max": 600,                // 最大显示文件数
    "skip": {
      "folder": [              // 跳过显示部分文件夹内文件
        "node_modules"
      ],
      "file": []               // 路过显示部分文件
    },
    "favend": {
      "efh": {
        "key": "efh",          // favend 访问关键字。efss/efh
        "name": "efh 初版",
        "type": "runjs",       // favend 类型。runjs 执行脚本|favorite 收藏目录
        "target": "elecV2P.efh",
        "enable": true         // 是否启用
      },
      "logs": {
        "key": "logs",
        "name": "查看日志",
        "type": "favorite",    // favorite 列出 target 目录下的所有文件
        "target": "logs",
        "enable": true
      }
    },
    "favendtimeout": 5000      // favend 脚本运行超时时间
  },
  "env": {                     // 环境变量相关设置(在 elecV2P 启动后自动添加
    "path": "",                // 该项内容会和 process.env.PATH 合并,并自动更新
    "acookie": "myappcookie"   // 其他环境变量会自动添加,process.env.acookie = "myappcookie"
  },
  "gloglevel": "info",         // 后台日志显示等级。可选值 error|notify|info|debug,默认 info
  "glogslicebegin": 5,         // 日志时间显示格式。0: 默认,5: 不显示年份,11: 不显示年月日
  "homepage": "http://127.0.0.1",      // 主页地址。用于 RSS 订阅及脚本中的 __home 参数
  "init": {
    "checkupdate": false,      // elecV2P 启动时,是否检测更新。默认 true
    "runjsenable": true,       // elecV2P 启动时,是否自动运行脚本(v3.7.4 增加)仅在为 false 时,表示不运行
    "runjs": ""                // elecV2P 启动时,自动运行脚本。多个脚本使用逗号(,)隔开,比如 test.js, 0body.js
  },
  "lang": "zh-CN",             // 语言偏好。zh-CN 中文|en 英文(多语言翻译龟速进行中... 
  "minishell": true,           // 是否开启 minishell。默认 false 关闭
  "SECURITY": {                // 安全相关设置
    "enable": false,           // 默认不开启(建议在首次打开 webUI 后手动启用
    "blacklist": [             // 禁止访问的 IP 列表。* 表示禁止所有访问(除了下面的 whitelist
      "*"
    ],
    "whitelist": [             // 允许访问的 IP 列表。优先级高于 blacklist
      "127.0.0.1",
      "::1"
    ],
    "cookie": {                // 是否允许通过 cookie 访问。仅当 enable 对应值为 false 时表示不允许
      "enable": true
    },
    "tokens": {                // 临时访问 token,可限制访问路径(v3.7.4 增加
      "md5(token)": {          // 临时 token 的 MD5 hash 对应值(启动后会进行自动修复
        "enable": true,        // 是否启用该临时 token
        "token": "xxxxx",      // 访问 token。比如:efss/hi?token=xxxx, /logs?token=xxxxx
        "path": "^/(efss|logs?)",       // 限制可访问的路径。匹配方式 new RegExp(path, 'i').test(req.path)。留空表示不限制
        "note": "给 xxx 的",   // 备注说明
        "times": 0             // 授权访问次数统计
      },
      "token2": {              // 支持设置多个临时访问 token。直接删除即可取消授权
        "enable": true,
        "token": "xxxxxx",     // 首次访问成功后,会生成一个有效期为 7 天的 cookie(增加 &cookie=long 有效期为 365 天
        "path": "/efss/hi"     // 生成的 cookie 可访问路径同样受此参数限制
      }
    },
    "numtofeed": 1,            // 有几次非法访问时出一个默认通知。0: 表示不通知
    "webhook_only": false      // 仅允许 webhook 接口访问
  },
  "TZ": "Asia/Shanghai",       // 时区设置。将会赋值到 process.env.TZ
  "update_check": false,       // 是否检测更新
  "update_check_gap": 0,       // 检测更新最低时间间隔。单位 ms。默认 1000*60*30(30 分钟
  "wbrtoken": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",         // webhook token,uuid 格式(建议)。当省略时自动生成。在启动时可通过 env.TOKEN 赋值
  "webhook": {
    "script": {                // 当 webhook type 类型无匹配时的最终处理脚本
      "enable": false,         // 更多说明参考 https://github.com/elecV2/elecV2P-dei/blob/master/docs/09-webhook.md
      "target": "webhook.js"
    },
    "token": "xxxxxxxx-xxx"    // 当前 wbrtoken 缺省时,会使用该值代替(v3.7.4 添加
  },
  "webUI": {                   // webUI 主界面相关设置
    "port": 80,                // webUI 运行端口
    "tls": {
      "enable": false,         // 是否开启 TLS 访问。默认 false 不启用。启用时,建议端口使用 443
      "host": "e.dev"          // 自签 TLS 证书域名
    },
    "logo": {
      "enable": true,          // 是否使用自定义 LOGO
      "src": "//x.xx/x.png",   // LOGO 替换。当该 LOGO 加载失败时,将生成默认 LOGO 头像
      "name": "elecV2"         // LOGO 旁边显示的文字
    },
    "nav": {                   // webUI 导航相关设置
      "overview": {
        "show": true,          // 是否显示该导航条目
        "name": "基础信息"     // 具体显示的导航文字
      },
      "task": {
        "show": true,
        "name": "定时任务"
      },
      "mitm": {
        "show": false
      },
      "rules": {
        "show": false
      },
      "rewrite": {
        "show": true,
        "name": "重写请求"
      },
      "jsmanage": {
        "show": true,
        "name": "脚本管理"
      },
      "setting": {
        "show": true,          // SETTING/设置 导航项默认不能关闭
        "name": "设置相关"
      },
      "cfilter": {
        "show": false,
        "name": ""
      },
      "about": {
        "show": false,
        "name": "简介说明"
      },
      "donation": {
        "show": true,
        "name": "赞助打赏"
      }
    },
    "theme": {                 // webUI 自定义主题(测试功能,部分用户可用
      "simple": {              // 当前应用主题
        "enable": false,       // 是否启用
        "name": "椰树背景",    // 主题名称
        "mainbk": "#2E3784",   // 主要背景色彩
        "maincl": "#6E77FB",   // 主要文字色彩
        "appbk": "url(https://images.unsplash.com/photo-1649256651398-46408de2f095?auto=format&fit=crop&w=1964)",    // 背景
        "style": ".eapp_item .eapp_name {color: var(--main-fc);}"       // 其他附加样式
      },
      "list": [{               // 保存的主题列表。对应值为上面 simple 中除 enable 外的其他值
      }]
    }
  },
  // 规则/脚本/常量等个人数据保存目录(v3.7.4 增加)
  // 对应值应为某个文件夹。当不存在时,将自动生成
  // 支持相对路径和绝对路径 path.resolve(__dirname, path_lists || 'script/Lists')
  "path_lists": "myLists",     // 规则、定时任务等保存文件夹
  "path_script": "/elecv2p/script",        // 脚本文件保存路径
  "path_store": "E:\\elecV2P\\Store",      // store/cookie 常量保存目录
  "path_shell": "./efss/myShell",          // shell 指令默认执行目录

  // 以下为 elecV2P 启动后自动生成的值,预先设置无效(v3.7.4 之后被全部移除
  "path": "/xx/config.json",   // 当前配置文件保存路径。path.join('./script/Lists/config.json')
  "start": 1666488300114,      // 当前 elecV2P 启动时间。Date.now()
  "userid": "md5hash",         // 用户 ID。对应值为 md5(webhook token)
  "version": "3.7.3",          // 当前版本。require('./package.json').version
  "vernum": 373,               // 当前版本的数字表达。Number(version.replace(/\D/g, ''))
  "newversion": "3.7.4"        // 检测到的新版本(如果存在的话
}
```

## 其他说明

配置文件可在启动时通过环境变量(env) **CONFIG** 来指定更改,最终路径为 path.resolve('script/Lists', process.env.CONFIG || 'config.json')。比如 **set CONFIG=123.json&&node index.js**, 则最终配置文件为 **xxx/script/Lists/123.json**。支持绝对路径,比如 **CONFIG=/elecV2P/config.json node index.js**,则最终配置文件为 **/elecV2P/config.json**。

### 启动时环境变量

在启动时使用以下环境变量(ENV),可增加或覆盖配置文件中的相关值。

- CONFIG : 指定配置文件路径
- PORT : webUI 使用端口
- PROXYEN : 启动时打开代理 anyproxy enable(v3.7.5 增加)
- TOKEN : webhook token,对应配置文件中的 wbrtoken 项
- TZ : 时区设置,默认 Asia/Shanghai

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/Advanced.md
================================================
```
最近更新: 2023-03-22
适用版本: 3.7.8
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/Advanced.md
```

## elecV2P 进阶使用篇

# 安全访问相关设置

位于 webUI->SETTING/设置相关 页面

![limitip](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/security.png)

- 默认处于关闭状态,所有 IP 可访问
- 该限制仅对 webUI 端口(默认 80)有效,对 8001/8002 对应端口无效
- 白名单的优先级高于黑名单。比如,当同个 IP 同时出现在白名单和黑名单中时,以白名单为准,即: 可访问
- IP 以换行符或英文逗号(,)作为分隔,保存实时生效
- 在黑名单中可用单个星号字符(\*)表示屏蔽所有不在白名单中的 IP,建议在公网部署的情况下使用
- 设置**仅开放 webhook 接口**后,可通过 webhook?token=xx&type=security&op=put&webhook_only=0 来关闭

IP 屏蔽后,可通过在请求链接中添加 **?token=webhook token** 的参数来绕过屏蔽,例如: http://你的服务器地址/?token=a8c259b2-67fe-4c64-8700-7bfdf1f55cb3 (服务器的 WEBHOOK TOKEN,首次启动时为随机值)

- 首次通过 token 访问时会在浏览器端生成一个 cookie,之后访问时不再需要 token(v3.5.1)
- cookie 默认有效期 7 天,在后面添加 ?token=xxx&cookie=long,有效期为 365 天
- 如果不想留下 cookie,请使用无痕模式(在使用他人或公共设备时
- 访问时后面添加 ?cookie=clear 删除当前设备的授权信息(v3.6.4)
- 在设置中取消 **允许 cookie 授权访问** 后,所有 cookie 将不可访问(v3.6.6)

## 临时访问 TOKEN(v3.7.4 增加

增加临时可访问 token,可限制访问路径。作用:

- EFSS 临时文件分享
- 部分脚本、日志临时分享
- 非安全网络下临时访问
- 其他

![temp_token](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/temp_token.png)

1. 访问 token。使用示例:efss/hi?token=xxxx, /logs?token=xxxxx
2. 限制可访问的路径。匹配方式 new RegExp(path, 'i').test(req.path)。留空表示不限制
3. 备注信息
4. 授权访问次数统计

注意事项:

- 当设置和 webhook token 相同值时,会自动舍弃该临时 TOKEN
- 当设置为空值时,会自动舍弃
- 当临时 token 有相同项时,仅保留最后一项
- 临时访问 token 同样会生成 cookie
  - cookie 有效期同上面的 webhook token(7 天或 365 天)
  - cookie 可访问路径同对应 token 的可访问路径

### 安全访问检测逻辑

1. 检测安全访问是否开启
  - 当没有开启时,允许所有请求
  - 当开启时,进入下一步
  - */favicon.ico 不进行安全检测*
2. 检测是否启用 webhook_only 选项(仅开放 webhook 接口
  - 当启用时,仅允许 /webhook 请求,其他请求返回 403 无授权访问提示信息
  - 当没有启用时,进入下一步
3. cookie 检测
  - 检测通过,允许请求
  - 检测失败,进入下一步
4. token 检测
  - 检测通过,允许请求。并将设置一个有效期为 7 天或 365 天的 cookie
  - 检测失败,进入下一步
5. IP 检测
  - 检测通过,允许请求(不会设置 cookie 信息
  - 检测失败,返回 403 无授权访问提示信息

# minishell

小型 shell 网页客户端,可执行一些简单的 shell 命令。比如: **ls**、 **python3 -V**、 **rm -rf \***、 **reboot** 等

![minishell](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/minishell.png)

## 开启方式

- v3.4.4 之后使用 webhook 快速开启关闭

```
// 查看当前 minishell 状态
http://127.0.0.1/webhook?token=xxxxbbff-1043-XXXX-XXXX-xxxxxxdfa05&type=devdebug&get=minishell

// 打开
http://127.0.0.1/webhook?token=xxxxbbff-1043-XXXX-XXXX-xxxxxxdfa05&type=devdebug&get=minishell&op=open

// 关闭
http://127.0.0.1/webhook?token=xxxxbbff-1043-XXXX-XXXX-xxxxxxdfa05&type=devdebug&get=minishell&op=close
```

- v3.4.4 之前的打开方式

方法一: 在配置文件中添加如下参数,然后重启 elecV2P。

``` JSON
{
  // ... 其他保持不变
  "minishell": true
}
```

方法二: 在 webUI 页面,打开浏览器开发者工具,在 Console 中执行以下代码,然后刷新页面。

``` JS
fetch('/config', {
  method: 'put',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "type": "config",
    "data": {
      "minishell": true
    }
  })
}).then(res=>res.text()).then(s=>console.log(s))
```

再打开 webUI,在 SETTING/设置相关 界面的右上角有一个蓝黑的小圈,点击即可打开 **minishell** 交互台。

## 基础使用

- minishell 的通信基于 websocket,确保执行任何命令前 websocket 是成功连接的
- minishell 的执行基于 nodejs 的 **[child_process exec](https://nodejs.org/api/child_process.html)**
- minishell 命令执行默认 timeout 为 60000ms(1 分钟)。可在结尾增加 -timeout=0 进行调整

elecV2P 对一些简单的命令会自动进行跨平台转换。比如,在 windows 平台输入 **ls** 命令,会自动转化为 **dir**。**reboot** 自动转化为 **restart-computer** 等。
更多跨平台命令自动转换持续添加中,欢迎反馈。

另外,如果指令中包含 http 链接,将会自动下载后再执行,比如命令:

``` sh
python3 -u https://raw.githubusercontent.com/elecV2/elecV2P/master/script/Shell/test.py
# 通过这种方式可以实现直接执行远程脚本
# 部分常用网络命令已排除下载,比如: curl/wget/git/start/you-get/youtube-dl 等
# 更多说明,可参考 06-task.md Shell 指令 部分
```

*如果在 windows 平台出现乱码,尝试执行命令: CHCP 65001*

## 特殊指令

- cls/clear   // 清空屏幕日志
- cwd         // 获取当前工作目录
- cd xxx      // 更改当前工作目录到xxx
- docs        // 打开此 minishell 说明页面(v3.4.7)
- exit        // 最小化 minishell 界面(在子进程交互中输入时表示结束子进程
- run         // RUN 运行指令(v3.6.7 添加

### 快捷按键

- esc         // 清空当前输入命令
- ctrl + l    // 清空屏幕日志
- ctrl + a    // 移动光标到命令开始处
- up/down     // 上下查找历史执行命令
- shift + tab // 移动光标到子进程交互输入框(如果存在的话
- 单击上方日志输出部分,停止自动滚动。单击下方命令输入部分,开启自动滚动

# 根据 MITM HOST 列表自动生成 PAC 文件

## 简介

PAC 文件链接: webUI/pac 。 比如 http://127.0.0.1/pac 或者 https://xx.xxx(你的webUI地址)/pac

代理地址指的是其他设备可以访问到的 ANYPROXY 代理地址及端口,如果 elecV2P 部署在本地,那么可能是 127/172/192/10 等开头的 IP 地址,比如 192.168.1.101:8101。 如果 elecV2P 部署在远程服务器上,那么就应该是一个远程 IP 地址加 ANYPROXY 对应端口。

PS: 可填写多个代理。比如 "127.0.0.1:8001; PROXY 1.2.3.4:5678; DIRECT",以上内容表示当第一个代理不可用时,使用第二个代理(后面还可以接多个),都不可用时使用直连(DIRECT)。
*注意:第一项可不填写 PROXY 字符,后面的代理必须用分号(;)隔开,且带上 PROXY 字符。*

未匹配到(NON-MATCHED)的网络请求(不需要 MITM),默认使用直连(DIRECT),也可以设置使用其他代理(v3.7.8)。比如填写:"127.0.0.1:7890; DIRECT",表示默认使用代理,当代理不可用时直连。

*更多关于 PAC 的说明,参考 MDN 文档: [代理自动配置文件(PAC)文件](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file)*

## 使用

在使用设备的代理设置部分,选择 PAC 自动代理,填写上面的 PAC 文件链接。该链接对应内容为一个根据当前 elecV2P MITM HOST 列表自动生成的 PAC 文件。

### 提醒

- 如果 webUI 开启了安全访问,在填写 PAC 链接时注意带上 token,建议设置临时 token 进行访问。比如 http://192.168.1.101/pac?token=1234
- 如果更新 mitmhost 列表后 PAC 文件没有更新,建议在 PAC 链接后添加任意参数进行缓存更新。比如 http://192.168.1.1/pac?token=1234&update=154
- 如果设置 PAC 后无法联网,请确认 PAC 默认代理地址及端口是否填写正确,以及 elecV2P 的 MITM 功能是否开启

# 其他进阶操作

- 开启 anyproxy 代理 websocket 请求: 在 script/Lists/config.json 中 anyproxy 部分添加

``` JSON
{
  "anyproxy": {
    // ... 前面保存不变,
    "wsIntercept": true
  }
}
```

- 查看 ANYPROXY 当前所启用的规则

http://127.0.0.1/webhook?token=xxxxbbff-1043-XXXX-XXXX-xxxxxxdfa05&type=devdebug&get=rule

- 查看当前连接客户端简易信息(v3.5.0)

http://127.0.0.1/webhook?token=xxxxbbff-1043-XXXX-XXXX-xxxxxxdfa05&type=devdebug&get=wsclient

### 说明文档列表

- [overview - 简介及安装](01-overview.md)
- [task - 定时任务](06-task.md)
- [rewrite - 重写网络请求](05-rewrite.md)
- [rules - 网络请求更改规则](03-rules.md)
- [script - 脚本编写及说明](04-JS.md)
- [Docker - Docker 运行相关](02-Docker.md)
- [feed&notify - 通知相关](07-feed&notify.md)
- [logger&efss - 日志和 EFSS 文件管理](08-logger&efss.md)
- [webhook - webhook 使用简介](09-webhook.md)
- [config - 配置文件说明](10-config.md)
- [Advanced - 高级使用篇](Advanced.md)


================================================
FILE: docs/Readme.md
================================================
## [elecV2P](https://github.com/elecV2/elecV2P) 使用说明

*可直接跳转到想了解的部分,不必按顺序查看*

1. [overview - 简介及基础安装](https://github.com/elecV2/elecV2P-dei/tree/master/docs/01-overview.md)
2. [Docker - Docker 运行相关](https://github.com/elecV2/elecV2P-dei/tree/master/docs/02-Docker.md)
3. [rules - 网络请求更改规则](https://github.com/elecV2/elecV2P-dei/tree/master/docs/03-rules.md)
4. [JS - 脚本编写及说明](https://github.com/elecV2/elecV2P-dei/tree/master/docs/04-JS.md)
5. [rewrite - 重写网络请求](https://github.com/elecV2/elecV2P-dei/tree/master/docs/05-rewrite.md)
6. [task - 定时任务](https://github.com/elecV2/elecV2P-dei/tree/master/docs/06-task.md)
7. [feed&notify - 通知相关](https://github.com/elecV2/elecV2P-dei/tree/master/docs/07-feed&notify.md)
8. [logger&efss - 日志和 EFSS 文件管理](https://github.com/elecV2/elecV2P-dei/tree/master/docs/08-logger&efss.md)
9. [webhook - webhook 使用简介](https://github.com/elecV2/elecV2P-dei/tree/master/docs/09-webhook.md)
10. [Advanced - 高级篇(开启 minishell 等)](https://github.com/elecV2/elecV2P-dei/tree/master/docs/Advanced.md)

### 两篇使用教程

- [elecV2P 基础使用之定时运行脚本](https://elecv2.github.io/#elecV2P%20%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8%E4%B9%8B%E5%AE%9A%E6%97%B6%E8%BF%90%E8%A1%8C%20JS)
- [elecV2P 进阶使用之抓包及 COOKIE 获取](https://elecv2.github.io/#elecV2P%20%E8%BF%9B%E9%98%B6%E4%BD%BF%E7%94%A8%E4%B9%8B%E6%8A%93%E5%8C%85%E5%8F%8A%20COOKIE%20%E8%8E%B7%E5%8F%96)

================================================
FILE: docs/dev_note/archive/favend JS 重构-efh.md
================================================
```
近期更新: 2021-12-09 22:10
```

### 原因

elecV2P favend 中的 JS 可能包含 html 部分,直接使用 JS 进行插入,不够优雅。主要是在写的时候,代码编译器默认是 JS 高亮模式,而 html 部分只能通过注释的方式编写,失去了高亮/补齐等特性。于是想可以通过类似 vue 或 react 的结构来设计此部分代码。

比如,html 部分使用 <template></template> 标签进行包裹,JS/api 返回部分使用 <script></script> 标签。当然具体重构的时候可以使用其他 elecV2P 专用标签,细节问题之后再考虑。该方式的主要问题是,此类 JS 文件是不能直接运行的,比如 .vue 文件需要转化才能执行。在 elecV2P 中这类类似 JS 的文件可以叫做,.euv/.evu/.evs/.ejs 等,同样此类细节问题之后再考虑,假如暂时命名为 .evs ,那么 evs 文件必须满足的一点是:可直接运行,至少是在 node 环境下可直接执行(因为 elecV2P 的 favend 本身就运行在 node 环境中)。

### 初步设计

如果按照 node 可运行的标准,其实 .vue 也可以(要不直接使用 vue?本篇完^\_^)。但是每次都使用 node 对 vue 文件进行转化,再返回,可能比较耗时和占用资源(没有具体测试过,耗时和资源占用,不过应该不大可行)。

按照最新的 ES6 module 标准,可以直接 import/export。(但这部分还没有仔细研究过 2021-09-20 15:02 ,得专门学习一下)。已知的是这种模式可以直接在浏览器中运行,不需要服务器提前编译。然后问题是:如何优雅的插入 html 代码(我们最初要解决的问题)。可直接 import html 文件吗?或者先载入 JS ,然后再载入 html?又或者直接将 html/css/js 打成一个包(module),这个包就是 backend 之前对应的 "JS"。然后这个包是应该以**文件夹**的形式存在,还是**文件**的形式?比如 .evs。

也许可以是一个以 .evs 结尾的文件夹?那么这个文件夹应该怎么以 **module** 的形式发送给前端页面呢?.evs 包含 xx.json 配置文件,指定 html 入口文件,然后 html 文件引入 js/css?

想得太多,而知道的太少。先去看一下 module 前端打包的相关知识。2021-09-20 15:13

### 待解决问题 2021-09-30 09:24

- JS 或者说模块的生命周期

### 基本模型 2021-10-19 18:43

假设是一种部分化的 HTML 页面,比如 iframe,但比 iframe 更小,只能是一个 div,这些 div 是基本模块,里面包含自身所需 css/js,以及可与后台交互的 js,且可以请求其他 div 模块,并且可与已有的基础页面通信,以及和后来请求的 div 模块通信。

首先基础页面可包含一个通用的 css 文件,比如 ant-design-vue 的 css,这个 css 是所有子 div 共用。然后也可以包含一个共用的 js 函数库,类似于 methods。另外还得包含一个远程函数库 **backend**(之后再找个比较合适的名字),里面包含的函数实际运行在后端,但在前台无感知。比如点击某个按钮,在后台移动/删除某个文件,但整个过程得让用户感觉和操作本地的数据一样。点击在前端执行 console.log(1),点击在后台执行 console.log(1),这两者在用户眼里并无区别(目标是这样的)。

一段伪代码:

``` JS
html: `
<button data-method='log' data-parm='1'>front 1<button>
<button data-method='log' data-type='backend' data-parm='2'>back 2<button>
`
methods: {
  log(data){
    console.log('front', data)
  }
}
backend: {
  log(data){
    console.log('back', data)
  }
}
```

问题:

html 编写仍然没有高亮部分(或许写个编辑器插件来解决?那么别人为什么要用你这个插件呢?进一步,别人为什么要用你这个 backend ?有什么优势?)

到底想实现的是一个什么样的东西?

其实我自己也不是太明白。很喜欢**云原生**这个概念,但这个概念好像都只是概念而已,看过一些所谓的云原生的产品,其实就是一台部署在云端的服务器或软件而已,好像和本地没有一点关联。
**小程序**把前端的很多部分都*本地化*了,但也产生了很多的屏障,每个平台搞一套语义标签一套 css一套 js 接口,在扩展/发扬 html(前端) 的同时,好像更像是在搞分裂。

**云原生概念** + **小程序概念** 可以搞出一个什么样的东西?
写程序不分前端和后台(像前面示例的点击运行 log 函数,不分是在前端运行还是后端运行),但这其实还是分前后台。更准备的说应该是不分前端开发和后端开发,开发,写一套程序同时可调用前端数据/函数和后台数据/函数。更进一步说,是写一段代码现时包含前端和后端部分,当需要使用前端相关功能时就使用前端部分,需要后台数据时,就使用后台部分。

那么,为什么要搞这么复杂呢?
其实在我看来这应该是变简单了。现在全栈开发好像有了一点点的趋势,但实际上的全栈其实就是打两份工而已。这边写后台代码,写完后又去写前端代码,这其实就这两个活是一个人干,还是两个人干的问题。那么这跟前面所说的又有什么有关系?

假如前端要向后台请求一个数据,很多时候,前端其实并不知道后台会返回什么样的数据,甚至可能是一个错误。这需要前后有效的沟通(这不是一件容易的事)。再看一段伪代码

``` JS
html: `
<button data-method='getUser'>GET user<button>
`
methods: {
  log(){
    let user = await fetchData('/username')
    alert(user)
  }
}
backend: {
  router('/username'){
    // maybe get name from some db
    // let data = db.get('userid')
    return 'elecV2P'
  }
}
```

主要目的:实现前后端代码的同时开发,真正的**全栈工程师**。

当然这种方式可能只适用于一些小的项目,但这就是原本的设计场景。一个小的 div 包含自身的 css/js,可与后台进行交互。

2021-10-19 19:33  未完待续

### elecV2P favend html(.efh) 2021-12-01 21:17

经过一段时间的思考,得到一个初步可能可行的方案(等优化完善。

以最初 html 页面为基础,增加标签 <script type="text/javascript" runon="elecV2P">此部分为后台代码(run on elecV2P)</script>

``` xml default.efh
<div>
  基础 html 部分
</div>
<script type="text/javascript">
  console.log('前端 JS')
  <!-- 从后台获取数据 同页面自定义 API -->
  fetch('?data=json').then(res=>res.json()).then(console.log)

  <!-- 假如引入 $fend(待完成) -->
  async function main() {
    let data = await $fend.get('json')
    console.log(data)
  }
</script>

<script type="text/javascript" runon="elecV2P">
  <!-- 可使用 src 属性引入本地或远程 JS -->
  console.log('后台 JS')

  if (JSON.parse($request.body).data === 'json')) {
    $done({
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
        'X-Powered-By': 'elecV2P'
      },
      body: {
        'hello': 'elecV2P favend',
        'note': '这是由 elecV2P favend 返回的 JSON 数据',
        'docs': 'https://github.com/elecV2/elecV2P/blob/master/efss/readme.md',
        'request': $request
      }
    })
  } else {
    $done('get method ' + $request.method)
  }

  <!-- 假如引入 $fend(还没写) -->
  $fend.set('json', { hello: 'from elecV2P $fend' })
</script>
```

执行过程/基本原理:
- 首次执行 .efh 文件时,先使用 cheerio 模块将 efh 文件分离为**前端和后端**,并进行缓存
- 然后当使用 get 请求主页时,直接返回**前端**代码
- 当前端使用 fetch 或 $fend 请求后台 API/数据时,执行**后端**代码并返回执行结果


优点:
- 前后端代码同一页面,方便开发者进行管理
- 标签高亮(最初要解决的问题
- 沿用 html 语法,没有额外的学习成本

缺点:
- 需要一个后台“引擎”对代码进行分离(开发者不需考虑

待优化部分:
- 前端 fetch,后台 $request 判断,不够简单/优雅。可引入变量进行统一,比如 $fend, 使用 $fend.get('key') 获取,使用 $fend.set('key', 'data') 绑定赋值
- 长连接/持续数据传输,关闭后自动清理缓存

问题:
- 和最初的 PHP/PYHTON 等后台生成前端页面有什么区别?

这是在 html(前端) 插入后台运行的代码

#### efh 细节实现 2021-12-06 20:23

- $fend

前端:$fend(key/attr/arg/params<string>, data<string\|object>)<promise>
后台:$fend(key, data<any>)

简单说明:
- key 可以看成是一个路由,或者是要从后台获取的关键值/API 等,前后端应该配对出现。
- 前端 $fend 第二参数 data 表示要传输给后台的值,可省略。
- 后台 $fend 第二个参数为 key 对应的返回值,可以是一个函数。当为函数时,此函数接收的第一项参数为前端传输过来的 data。

使用示例:

``` JS
// 前端部分
$fend('newone').then(res=>res.text()).then(console.log);

$fend('skey', '传输给后台的数据').then(res=>res.text()).then(alert).catch(e=>console.error(e));

// 后台部分
$fend('newone', $store.get('newone'));

$fend('skey', d=>{
  console.log('收到前台传输数据:', d);

  return {
    ok: true,
    data: d,
    message: '后台返回数据,可以是 string 或 object'
  }
})
```

*通常情况建议只使用一对 $fend 来交互数据,使用第二个参数 data 来确定数据内容。*

底层实现:(这部分由平台开发者完成,在编写 efh 时直接使用上面的示例形式调用即可)

前端 $fend 为封装了 fetch 的函数,后台 $fend 在 context 中实现。

``` JS
// 前端部分
function $fend(key, data) {
  return fetch('', {
    method: 'post',
    body: JSON.stringify({
      key, data
    })
  })
}

// 后台部分
CONTEXT.final.$fend = async function (key, fn) {
  // 有 bind this, 勿改写为 arrow function
  if (typeof this.$request === 'undefined') {
    return this.$done('$request is expect');
  }

  let body = this.$request.body;
  if (!key || !body) {
    return this.$done('$fend key and body is expect');
  }
  try {
    body = JSON.parse(body);
  } catch(e) {
    return this.$done('a object string of $request.body is expect');
  }
  if (body.key === key) {
    if (typeof fn === 'function') {
      try {
        fn = await fn(body.data);
      } catch(e) {
        fn = '$fend ' + key + ' error: ' + e.message;
        console.error('$fend', key, 'error', e);
      }
    }
    return this.$done(fn);
  }
}.bind(CONTEXT.final);
```

待优化:

- 其他类型数据 arrayBuffer/stream 等
- $fend 后台无匹配时返回结果
- $fend key/路由 配对优化


================================================
FILE: docs/dev_note/archive/minishell subprocess.md
================================================
### minishell 子进程交互

init subprocess list

前提:每条 command 起一个 id

send('shell', {
	id,
	type: 'main|sub',
	data: 'command',
})

子进程为一系列,透明背景,划过可输入背景提醒

commandId = this.$wsrecv.id _ from _ order

subprocess {
	commandId: {
  	command: python3 te.py
	  history: {
      current: -1,
      lists:[]
    }
	}
}

childexec.on('exit', subprocess commandId remove)

### exec 所有进程统计

- from + id

================================================
FILE: docs/dev_note/archive/webUI.md
================================================
# web 重构计划(基本完成)

## 左侧导航栏菜单 menu

- OVERVIEW
  - Port web/proxy/查看请求(anyproxy)
  - rules/rewrite/task/mitm list lenght
- RULES
  - elecV2P default.list
- REWRITE
  - Table url file.js
- JSMANAGE
  - JS upload
  - filelist editor
- TASK
  - overview task list/table name,type,time,job,stat/controll
  - new task form table
- MITM
  - rootCA
  - mitm host

- CFILTER
- SETTING
- ABOUT
- DONATION

## 未来计划

- [x] 去 ant design vue
- [x] 移动端优化

### 右键菜单 contextmenu.vue

``` XML
<contextmenu :menus='menu.list' :x='menu.x' :y='menu.y' />
```

- x <number> : x 坐标
- y <number> : y 坐标
- menus <array> : 菜单内容
  - 菜单选项 <object> (建议始终设置 label,其他项视情况添加)
    - label <string> : 菜单显示文字
    - click <function> : 点击文字后执行函数
    - rclick <function> : 右键菜单后执行函数
    - dclick <function> : 双击菜单后执行函数
    - color <string> : 菜单选项颜色
    - bkcolor <string> : 菜单选项背景颜色
    - fontsize <string> : 菜单选项文字大小
  - 菜单选项 <object> 同上
  - ...

================================================
FILE: docs/dev_note/archive/websocket 通信协议设计.md
================================================
### websocket 通信协议设计

基础: json-RPC
参考: telegram api

### 内部函数管理/初始化

函数和数据分离

服务器端
- 处理客户端发送过来的数据,对应 method
- 更新 method
- 添加 临时 method / 传输函数

``` JS
const wsSer = {
  methods: {    // 现有方法集
    add(){      // 添加新的方法

    }
  }
}
```

### 基础数据传输结构:

客户端发送: client.send
- 发送者 sender
- 发送模块(单元) unit
- 数据类型(调用函数)
- 函数参数
- 需要返回 ?

服务器接收: server.recv
- methods
- reply_to all/sender/unit

服务器发送: server.send
- 指定接收者 (sender/unit)
- 数据类型(调用函数)
- 函数参数
- 需要返回 ?

客户端接收: client.recv
- 接收函数 methods
- reply ?


``` 接收
{
  methods: 'newmthod',
  param
}
```

## 需要实现的功能

- 生成 send 函数。用于向前端网页的某一个单元发送消息
- 生成 recv 函数。用于接收消息并处理

- 

================================================
FILE: docs/dev_note/clash delegate efh.md
================================================
### clash_delegate.efh

clash webUI for elecV2P, work on favend.

两个模块

- delegate manage
- delegate rule

默认分流 delegate - elecV2P

================================================
FILE: docs/dev_note/elecV2P 错误自检指南.md
================================================
### 后端运行问题

- 查看 errors.log
- 查看 pm2-error.log
- 查看对应脚本的日志文件
- 查看后台运行日志(docker logs elecv2p)
- webUI->SETTING->日志等级调整为 debug
- 在 Google/百度/搜狗 等搜索引擎上输入错误信息

- 重启/重装 elecV2P

### 前端显示问题

尝试升级或切换浏览器(部分老旧的浏览器无法解析一些新的 JS 语法。)

如果问题依然存在,请打开浏览器的**开发者调试工具**(PC 端快捷键通常为 F12,移动端查看各浏览器的说明)。然后将显示的错误信息反馈到 [这里](https://github.com/elecV2/elecV2P/issues)

### 其他常见问题

- anyproxy 网络请求有时候点击无反应(8002 端口

1. anyproxy 缓存已清除
2. anyproxy 已停止运行


- 如果你经常碰过某个问题,欢迎提交 issue 或 pr

### 如果问题依然存在,[open a issue](https://github.com/elecV2/elecV2P/issues)

================================================
FILE: docs/dev_note/ev 命令行程序.md
================================================
### ev binary 可执行程序

elecV2P 可执行程序

形式,要实现的功能

``` sh
ev -help, -h     # 帮助菜单

# elecV2P 全局控制类
ev start     # 开始 elecV2P
ev stop      # 停止 elecV2P
ev update    # 更新 elecV2P

## 可能
ev install   # 可选择 node 安装 还是 Docker
ev i         # 同上
  # options
  # -o nodejs | docker
  # -e Asia/Shanghai
  # -p 8100/8101/8102

# 状态类
ev status    # 显示状态
ev save      # 保存信息

# 配置类
ev config    # 列出配置
ev config webUI    # 列出某项配置
ev config set minishell 1    # 修改某项配置

# 执行类
ev xxxx.js   # 以 elecV2P 模式运行脚本
ev run xxx.js      # 同上
ev download url    # 下载文件
```

### 具体实现

node cmd binary

================================================
FILE: docs/dev_note/favend JS 重构-efh.md
================================================
```
近期更新: 2021-12-09 22:10
```

### 原因

elecV2P favend 中的 JS 可能包含 html 部分,直接使用 JS 进行插入,不够优雅。主要是在写的时候,代码编译器默认是 JS 高亮模式,而 html 部分只能通过注释的方式编写,失去了高亮/补齐等特性。于是想可以通过类似 vue 或 react 的结构来设计此部分代码。

比如,html 部分使用 <template></template> 标签进行包裹,JS/api 返回部分使用 <script></script> 标签。当然具体重构的时候可以使用其他 elecV2P 专用标签,细节问题之后再考虑。该方式的主要问题是,此类 JS 文件是不能直接运行的,比如 .vue 文件需要转化才能执行。在 elecV2P 中这类类似 JS 的文件可以叫做,.euv/.evu/.evs/.ejs 等,同样此类细节问题之后再考虑,假如暂时命名为 .evs ,那么 evs 文件必须满足的一点是:可直接运行,至少是在 node 环境下可直接执行(因为 elecV2P 的 favend 本身就运行在 node 环境中)。

### 初步设计

如果按照 node 可运行的标准,其实 .vue 也可以(要不直接使用 vue?本篇完^\_^)。但是每次都使用 node 对 vue 文件进行转化,再返回,可能比较耗时和占用资源(没有具体测试过,耗时和资源占用,不过应该不大可行)。

按照最新的 ES6 module 标准,可以直接 import/export。(但这部分还没有仔细研究过 2021-09-20 15:02 ,得专门学习一下)。已知的是这种模式可以直接在浏览器中运行,不需要服务器提前编译。然后问题是:如何优雅的插入 html 代码(我们最初要解决的问题)。可直接 import html 文件吗?或者先载入 JS ,然后再载入 html?又或者直接将 html/css/js 打成一个包(module),这个包就是 backend 之前对应的 "JS"。然后这个包是应该以**文件夹**的形式存在,还是**文件**的形式?比如 .evs。

也许可以是一个以 .evs 结尾的文件夹?那么这个文件夹应该怎么以 **module** 的形式发送给前端页面呢?.evs 包含 xx.json 配置文件,指定 html 入口文件,然后 html 文件引入 js/css?

想得太多,而知道的太少。先去看一下 module 前端打包的相关知识。2021-09-20 15:13

### 待解决问题 2021-09-30 09:24

- JS 或者说模块的生命周期

### 基本模型 2021-10-19 18:43

假设是一种部分化的 HTML 页面,比如 iframe,但比 iframe 更小,只能是一个 div,这些 div 是基本模块,里面包含自身所需 css/js,以及可与后台交互的 js,且可以请求其他 div 模块,并且可与已有的基础页面通信,以及和后来请求的 div 模块通信。

首先基础页面可包含一个通用的 css 文件,比如 ant-design-vue 的 css,这个 css 是所有子 div 共用。然后也可以包含一个共用的 js 函数库,类似于 methods。另外还得包含一个远程函数库 **backend**(之后再找个比较合适的名字),里面包含的函数实际运行在后端,但在前台无感知。比如点击某个按钮,在后台移动/删除某个文件,但整个过程得让用户感觉和操作本地的数据一样。点击在前端执行 console.log(1),点击在后台执行 console.log(1),这两者在用户眼里并无区别(目标是这样的)。

一段伪代码:

``` JS
html: `
<button data-method='log' data-parm='1'>front 1<button>
<button data-method='log' data-type='backend' data-parm='2'>back 2<button>
`
methods: {
  log(data){
    console.log('front', data)
  }
}
backend: {
  log(data){
    console.log('back', data)
  }
}
```

问题:

html 编写仍然没有高亮部分(或许写个编辑器插件来解决?那么别人为什么要用你这个插件呢?进一步,别人为什么要用你这个 backend ?有什么优势?)

到底想实现的是一个什么样的东西?

其实我自己也不是太明白。很喜欢**云原生**这个概念,但这个概念好像都只是概念而已,看过一些所谓的云原生的产品,其实就是一台部署在云端的服务器或软件而已,好像和本地没有一点关联。
**小程序**把前端的很多部分都*本地化*了,但也产生了很多的屏障,每个平台搞一套语义标签一套 css一套 js 接口,在扩展/发扬 html(前端) 的同时,好像更像是在搞分裂。

**云原生概念** + **小程序概念** 可以搞出一个什么样的东西?
写程序不分前端和后台(像前面示例的点击运行 log 函数,不分是在前端运行还是后端运行),但这其实还是分前后台。更准备的说应该是不分前端开发和后端开发,开发,写一套程序同时可调用前端数据/函数和后台数据/函数。更进一步说,是写一段代码现时包含前端和后端部分,当需要使用前端相关功能时就使用前端部分,需要后台数据时,就使用后台部分。

那么,为什么要搞这么复杂呢?
其实在我看来这应该是变简单了。现在全栈开发好像有了一点点的趋势,但实际上的全栈其实就是打两份工而已。这边写后台代码,写完后又去写前端代码,这其实就这两个活是一个人干,还是两个人干的问题。那么这跟前面所说的又有什么有关系?

假如前端要向后台请求一个数据,很多时候,前端其实并不知道后台会返回什么样的数据,甚至可能是一个错误。这需要前后有效的沟通(这不是一件容易的事)。再看一段伪代码

``` JS
html: `
<button data-method='getUser'>GET user<button>
`
methods: {
  log(){
    let user = await fetchData('/username')
    alert(user)
  }
}
backend: {
  router('/username'){
    // maybe get name from some db
    // let data = db.get('userid')
    return 'elecV2P'
  }
}
```

主要目的:实现前后端代码的同时开发,真正的**全栈工程师**。

当然这种方式可能只适用于一些小的项目,但这就是原本的设计场景。一个小的 div 包含自身的 css/js,可与后台进行交互。

2021-10-19 19:33  未完待续

### elecV2P favend html(.efh) 2021-12-01 21:17

经过一段时间的思考,得到一个初步可能可行的方案(等优化完善。

以最初 html 页面为基础,增加标签 <script type="text/javascript" runon="elecV2P">此部分为后台代码(run on elecV2P)</script>

``` xml default.efh
<div>
  基础 html 部分
</div>
<script type="text/javascript">
  console.log('前端 JS')
  <!-- 从后台获取数据 同页面自定义 API -->
  fetch('?data=json').then(res=>res.json()).then(console.log)

  <!-- 假如引入 $fend(待完成) -->
  async function main() {
    let data = await $fend.get('json')
    console.log(data)
  }
</script>

<script type="text/javascript" runon="elecV2P">
  <!-- 可使用 src 属性引入本地或远程 JS -->
  console.log('后台 JS')

  if (JSON.parse($request.body).data === 'json')) {
    $done({
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
        'X-Powered-By': 'elecV2P'
      },
      body: {
        'hello': 'elecV2P favend',
        'note': '这是由 elecV2P favend 返回的 JSON 数据',
        'docs': 'https://github.com/elecV2/elecV2P/blob/master/efss/readme.md',
        'request': $request
      }
    })
  } else {
    $done('get method ' + $request.method)
  }

  <!-- 假如引入 $fend(还没写) -->
  $fend.set('json', { hello: 'from elecV2P $fend' })
</script>
```

执行过程/基本原理:
- 首次执行 .efh 文件时,先使用 cheerio 模块将 efh 文件分离为**前端和后端**,并进行缓存
- 然后当使用 get 请求主页时,直接返回**前端**代码
- 当前端使用 fetch 或 $fend 请求后台 API/数据时,执行**后端**代码并返回执行结果


优点:
- 前后端代码同一页面,方便开发者进行管理
- 标签高亮(最初要解决的问题
- 沿用 html 语法,没有额外的学习成本

缺点:
- 需要一个后台“引擎”对代码进行分离(开发者不需考虑

待优化部分:
- 前端 fetch,后台 $request 判断,不够简单/优雅。可引入变量进行统一,比如 $fend, 使用 $fend.get('key') 获取,使用 $fend.set('key', 'data') 绑定赋值
- 长连接/持续数据传输,关闭后自动清理缓存

问题:
- 和最初的 PHP/PYHTON 等后台生成前端页面有什么区别?

这是在 html(前端) 插入后台运行的代码

#### efh 细节实现 2021-12-06 20:23

- $fend

前端:$fend(key/attr/arg/params<string>, data<string\|object>)<promise>
后台:$fend(key, data<any>)

简单说明:
- key 可以看成是一个路由,或者是要从后台获取的关键值/API 等,前后端应该配对出现。
- 前端 $fend 第二参数 data 表示要传输给后台的值,可省略。
- 后台 $fend 第二个参数为 key 对应的返回值,可以是一个函数。当为函数时,此函数接收的第一项参数为前端传输过来的 data。

使用示例:

``` JS
// 前端部分
$fend('newone').then(res=>res.text()).then(console.log);

$fend('skey', '传输给后台的数据').then(res=>res.text()).then(alert).catch(e=>console.error(e));

// 后台部分
$fend('newone', $store.get('newone'));

$fend('skey', d=>{
  console.log('收到前台传输数据:', d);

  return {
    ok: true,
    data: d,
    message: '后台返回数据,可以是 string 或 object'
  }
})
```

*通常情况建议只使用一对 $fend 来交互数据,使用第二个参数 data 来确定数据内容。*

底层实现:(这部分由平台开发者完成,在编写 efh 时直接使用上面的示例形式调用即可)

前端 $fend 为封装了 fetch 的函数,后台 $fend 在 context 中实现。

``` JS
// 前端部分
function $fend(key, data) {
  return fetch('', {
    method: 'post',
    body: JSON.stringify({
      key, data
    })
  })
}

// 后台部分
CONTEXT.final.$fend = async function (key, fn) {
  // 有 bind this, 勿改写为 arrow function
  if (typeof this.$request === 'undefined') {
    return this.$done('$request is expect');
  }

  let body = this.$request.body;
  if (!key || !body) {
    return this.$done('$fend key and body is expect');
  }
  try {
    body = JSON.parse(body);
  } catch(e) {
    return this.$done('a object string of $request.body is expect');
  }
  if (body.key === key) {
    if (typeof fn === 'function') {
      try {
        fn = await fn(body.data);
      } catch(e) {
        fn = '$fend ' + key + ' error: ' + e.message;
        console.error('$fend', key, 'error', e);
      }
    }
    return this.$done(fn);
  }
}.bind(CONTEXT.final);
```

待优化:

- 其他类型数据 arrayBuffer/stream 等
- $fend 后台无匹配时返回结果
- $fend key/路由 配对优化

### $fend.on('message', ()=>{}) 2022-07-31

接收服务器端发送的数据,基于 sse

*客户端服务器自动协商通信*(how?

``` JS
$fend.on = (event, fn)=>{

}
let ee = new EventSource('/sse/elecV2P/' + this.id)
```

================================================
FILE: docs/dev_note/favend 模块化.md
================================================
*将 EFSS 的 favorite 和 backend 结合起来有没有搞头*

### 原因

虽然 efh 文件很好的解决了前后端配合的问题,但当大量引用外部文件时,可能比较分散。比如在当前目录下引入 js 文件,在 efss 中引入 css/图像等其他类型的文件。于是考虑将 favorite 做为前端,backend 做为后台,打包成一个模块(module),放到同一个目录下,方便对各种资源进行管理。

### 大概工作过程

首先设置某个文件夹,假设为 **efssmod**,为模块入口。模块中以 index.html 做为前端入口,这样相关的 js/css/图片等资源都可以理所应当的放到同一目录下。然后再以 favend.js 做为后台入口。

具体的前后端入口文件待定,考虑引入 json 配置文件,类似于 package.json,以 index 关键字指定前端入口文件,以 backend 关键字指定后台入口文件,同时可以添加一些版本、注释、作者等相关信息,总之参考 nodejs 模块 package.json。

### 目前存在的问题

- 后台脚本暂时限制在 script/JSFile 目录,efss 目录的后台脚本无法获取执行
- 模块中的脚本不方便引入 script/JSFile 目录下的脚本。(可正常引入其他 nodejs 模块

### 假如完成

合并后的模块即 APP,未来可期。

### 其他想法

favend 片段化,方便结合 **evui**。不必生成整个网页,而只是一部分 html, 或者说 div 代码。方便在 elecV2P 的主界面中进行插入。

================================================
FILE: docs/dev_note/readme.md
================================================
### elecV2P 开发笔记

有时候想到一个功能,得先捋清一下思路,才能开始码代码。有些功能可能比较复杂,所以用笔记的形式记录一下。

当时的笔记可能不是最终实现的样子,仅供参考(其实也没有什么参考意义,如果感兴趣的话,可以稍微看看

总之,这就是在开发过程中,为了捋清楚某些开发步骤而作的一些笔记。

PS: 如果你在代码中看到一些“不合理”的地方,那可能是开发者“学习时”留下的记录。

================================================
FILE: docs/dev_note/runJSFile 执行逻辑及优化.md
================================================
### runJSFile 函数逻辑

执行的脚本类型

- 本地文件
- 远程文件
- rawcode

命名

runJSFile(filename, addContext={})

```
type       filename                        rawjs
- rawjs    rename filename rawcode.js      rawjs
- local    rename filename                 Jsfile.get
- remote   rename surlName                 Jsfile.get
```

addContext.filename vs addContext.rename
renmae 会重新写入文件

addContext.timeout: 只提前返回,不限制运行时间(已修改为限制运行时间,同步模式下

================================================
FILE: docs/dev_note/script_store.efh 应用中心.md
================================================
### 基础格式

参考苹果、安卓应用中心的分类模式。

``` JSON
{
  "category": "类别",
  "scripts": [
    {
      "name": "name.js",
      "logo": "url/to/logo_180x180.png",
      "note": "一些备注,关于脚本的说明",
      "tags": ["elecV2P", "标签"],
      "author": "elecV2",
      "homepage": "url/to/author",
      "resource": "url/to/file.js",
      "thumbnail": ["url/to/thum1.jpg", "url/to/thum2.jpg"],
      "sponsor_img": "url/to/qr.png",
      "sponsor_txt": "捐赠支持说明/或其他账号",
      "content_hash": "auto content md5 hash",
      "date_created": "2022-03-08 20:35",
      "date_updated": "2022-03-08 20:36",
    }
  ],
  "resources": [
    "https://wogowoodk.json"
  ]
}
```

``` JSON main.json
// 主入口文件
{
  "category": [
    {
      "name": "分类名称",
      "note": "分类描述",
      "resources": [
        "url1/to/scripts.json",
        "url2/to/scripts.json"
      ]
    }, {
      "name": "另一分类",
      "note": "分类描述",
      "resources": [],
    }
  ]
}
```

### 细节实现

- 主入口 main.json
- 分段获取多 json 文件
- 智能化标签自动生成 json 片断
- 新建 GITHUB 库存放 JSON
- 默认 LOGO(无 logo 时替换
- 用户上传发布(PR
- 个人脚本管理(PR
- 搜索 HASHTREE(自动生成更新
- 快速定位脚本,获取内容
- 增加任务/重写订阅的分类

### 工具

- content hash
- 快速生成 LOGO(基于 hash
- 自动生成上传日期
- 自动检测文件大小
- 自动生成标签列表

================================================
FILE: docs/dev_note/service workers 开发与优化.md
================================================
```
最近更新: 2022-09-03 09:20
文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/dev_note/service%20workers%20开发与优化.md
```

### 缓存策略 cache strategies

0. cache first, fetch fallback

优先使用缓存,缓存不存在则发送网络请求 fetch。

适用于图片等长期不更新的静态资源。

1. cache first, fetch synchronize

优先使用缓存,同时发送网络请求更新缓存数据。

适用于更新较频繁,但前端并不一定要求最新的资源。比如一些广告数据。

2. fetch first, cache fallback

优先使用网络请求,请求失败后使用缓存替换。

适用于要求最新资源,但失败后可使用缓存替换的内容。

3. cache first, random/schedule fetch update

优先使用缓存,随机或者间断固定时间后更新

其他:

- cache only/fetch only 没必要
- 请求直接返回,不经过 service worker (strategy -1)

### 资源匹配 what to cache

匹配方式:
- url
- path
- host
- mode
- search
- destination

为提高匹配效率,尽量使用全等匹配(includes),不要使用正则(new RegExp().test())

``` JS
const CACHE_URL = new Map([['url', 1]])

let strategy = -1

// 匹配顺序待研究
switch (true) {
case CACHE_URL.has(request.url):
  strategy = CACHE_URL.get(request.url);
  break;
case CACHE_PATH.has(request.pathname):
  strategy = CACHE_PATH.get(request.pathname);
  break;
}
```

匹配顺序逻辑:按资源精确程度。

比如完整的 url 匹配精确度最高,放在最前面。

接下来是应该是 url 中的 search 部分,然后是 path 部分,然后是 host 部分,然后是 资源类型(destination),然后是访问模式(mode)。

这是大概是匹配顺序,但应该根据实际项目进行调整。

### 关于 preload

仅在 event.request.mode === 'navigate' 的情况下发生

### 问题记录

- 白屏

网络问题无加载,使用 cache first

- PWA 不更新



### 参考资料

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
https://developer.chrome.com/docs/workbox/
https://googlechrome.github.io/samples/service-worker/basic/
https://web.dev/service-worker-lifecycle/
https://phyks.me/2019/01/manage-expiration-of-cached-assets-with-service-worker-caching.html

================================================
FILE: docs/dev_note/sse 通信模块.md
================================================
### server-sent events

需求分析:

- send message(or no)

问题:

- 多用户(同一个请求路径
- 多连接(单用户多个请求路径
- 多请求(单连接发送多次数据
- 多函数(不同数据对应不同处理函数
- 多参数(不同处理函数包含不同参数

如何区分?

作以下限制:

单页面只能有一个连接(不同页面的同一路径请求如何区分?
页面刷新后如何复用的问题

### 要实现的功能

- 服务器端

``` JS
/** clients 数据结构
{
  target1: {
    euid: res, euid: res, euid: res
  },
  target2: {euid: res},
}
可使用 query 指定 euid,方便重复使用(断开后重连
优势:
- 自定义多路径
- 同一路径多个请求
***/
class sse {
  constructor({ app }) {
    this.clients = new Map();
    if (app) {
      app.get('/sse/:target', (req, res)=>{
        // 同一 target 只能对应一个 clients
        // main/message 多个对应(取消)
        if (this.clients.has(req.params)) {
          clog.info('end old sse connection', req.params);
          this.clients.get(req.params).end();
        }
        res.writeHead(200, {
          'Content-Type': 'text/event-stream',
          'Connection': 'keep-alive',
          'Cache-Control': 'no-cache'
        });
        clog.info('new sse connection on', req.params);
        this.clients.set(req.params, res);
        req.on('close', ()=>{
          // this.clients.get(req.params).end();
          this.clients.delete(req.params);
        })
      })
    } else {
      clog.error('express app is expect');
    }
  }

  sent(target, message) {
    if (this.clients.has(target)) {
      let res = this.clients.get(target);
      if (message === 'end') {
        res.end();
        return;
      }
      res.write(JSON.stringify(message));
      return;
    }
    clog.error('sse connection', target, 'not ready yet');
  }
}

sse.send(target, message);
// sse.send('main', {})
```

- 客户端

``` JS
sse.recv(target, message=>{
  handle(message);
  done();
})
```

### 其他实现

假如分两种连接类型:

- 单路径单连接 /sse/efss
- 单路径多连接 /sse

================================================
FILE: docs/dev_note/store 常量加密存储读取.md
================================================
### 目的

常量只属于某个脚本,或者必须提供某个密码才能查看。

$store.put('value 原始', 'secret_key', {
  pass: 'owowogld',
  algo: 'ebuf',
})

$store.get('secret_key', {
  pass: 'owowogld',
  algo: 'ebuf',
})

### 算法基础

参考:https://elecv2.github.io/#算法研究之非对称加密的简单示例



### 未来计划

- 默认加密存储常量

================================================
FILE: docs/dev_note/webUI transparent mode.md
================================================
## 功能

将 webUI 临时作为一个“透明”代理,转发请求到其他任意端口,甚至任意服务器。

## 配置

``` JSON
"transparent": {
  "enable": true,
  "host": "127.0.0.1",    // 转发的目标服务器
  "port": 8001,           // 目标服务器端口
  "tls": false,           // 目标服务器是否通过 tls 连接
  "type": "proxy"         // 目标服务器类型 web - 网页, proxy - 代理, transparent - 另一个 elecV2P 透明代理
}
```

*(如无特殊说明,以下“透明代理”都特指 elecV2P webUI 透明代理)*

## 使用

直接将 webUI 当作代理使用。例如,假设 webUI 运行在 127.0.0.1:8000,则直接将 127.0.0.1:8000 当作代理地址填写到代理软件进行使用。

### 问题

- 当 webUI 只能通过 https 连接时,无法将这个 https 地址作为代理使用

考虑通过本地的透明代理连接远程透明代理,形成链式转发。

- 转发 http(s) 到另一个 https 透明代理

简单测试,如果另一个透明代理没有使用 nginx 等“网关”工具,好像是可以成功的。(待进一步测试)。

当转发普通 http 请求到有“网关”的透明代理端口时,部分“网关”服务会自动对请求进行一些修改,导致请求转发到了 webUI 端口。

### 分析

webUI 端口本身有两种情况:http https
端口透明目标站有三种情况:普通网站(web),代理(proxy),另一个透明端口(transparent)



================================================
FILE: docs/dev_note/webUI 主题设计.md
================================================
### 基本格式

``` JSON
{
  "themeone": {
    "name": "主题名称",
    "note": "一些说明",
    "color": {
      "--main-bk": "#003153",
      "--main-fc": "#FBFBFF",
      "--secd-bk": "#A7A8BD88",
      "--secd-fc": "#003153B8",
    },
    "logo": "url",
    "bkimage": "background-image url",
  }
}
```

### 包含内容

- 颜色(文字/背景等

以下为可选项(可能
- 圆角大小
- 图标 logo
- 标题 title

### 具体实现

*最后实现使用其他的实现方式*

```
.theme--name {
  --main-bk: #003153;
  --main-fc: #FBFBFF;
  --main-cl: #1890FF;
  --secd-bk: #A7A8BD88;
  --secd-fc: #003153B8;
  ...
}

document.body.className = 'theme--name'
```

### 自定义主题

``` JS
// TEST 简单测试代码
document.head.insertAdjacentHTML('beforeend', `<style>
#app {
  --main-bk: #326733;
  background: url(https://images.unsplash.com/photo-1646505183416-f3301d2a8127);
}

.section > .sider,
.sider_trigger.sider_trigger--mobile,
.section, .content, .header, .footer {
  background: transparent;
}
</style>`)
// #app {--main-bk: #2E3784;--main-fc: #FFCB40;--main-cl: #64AAD0;}
// #app {--main-bk: #2e1630;--main-fc: #e9bb4c;--main-cl: #d3fd3c;}

// background: #222E
// background: #0008
app.style.setProperty('--main-bk', '#222E')
```

前端设置 UI:

NAEM 主色彩 导出 启用 删除

### 最终实现方案

``` JSON
"theme": {
  "simple": {
    "enable": true,
    "mainbk": "#123456",
    "appbk": "https://xxx",
    "elebk": "transparent",
    "style": "#app{}",
  },
  "list": [{
    "name": "主题名称",
    "mainbk": "#123456",
    "appbk": "https://xxx",
    "elebk": "transparent",
    "style": "#app{}",
  }, {
    "name": "主题名称",
    "mainbk": "#2E3784",
    "appbk": "https://xxx",
    "elebk": "transparent",
    "style": "#app{}",
  }]
}
```

### Todo

- [x] 主题保存切换
- [x] 主题导入导出
- [x] 自定义 style

================================================
FILE: docs/dev_note/webUI 首页快捷运行程序 eapp.md
================================================
### 基础说明

![EAPP](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/eapp_overview.png)

一个类似于手机主界面的模块,点击图标快速执行 elecV2P 部分功能。或者作为其他网页应用的一个入口。

暂命名为 eapp - elecV2P application, 大写为 EAPP。

### 功能

- 运行 JS 脚本
- 运行 EFH 文件
- 执行 SHELL 指令
- 打开某个网址
- 前端执行代码 EVALRUN (v3.7.0 添加)

### 格式

``` JSON
[{
  "name": "软更新",
  "logo": "efss/logo/soft_update.png",   // 可省略。省略时将自动生成
  "type": "js",
  "target": "https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/softupdate.js",
  "hash": "md5hash",        // 自动生成
}, {
  "name": "显示名称",
  "logo": "https://raw.githubusercontent.com/elecV2/elecV2P/master/efss/logo/elecV2P.png",
  "type": "efh",
  "target": "https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/efh/notepad.efh",
}, {
  "name": "项目主页",
  "type": "url",
  "target": "https://github.com/elecV2/elecV2P"
}, {
  "name": "Shell 指令",
  "type": "shell",
  "target": "node -v"
}, {
  "name": "交互输入",
  "type": "shell",
  "target": "ls -cwd %ei%"    // v3.6.8 增加 %ei% 占位符,用于简单交互输入。ei(eapp input)
}, {
  "name": "eval 执行",
  "type": "eval",             // v3.7.0 增加使用 eval 函数在前端网页上执行 JS 代码
  "target": "alert('hello elecV2P')"
}, {
  "name": "PM2LS",
  "type": "shell",
  "target": "pm2 ls",
  "run": "auto",              // v3.7.3 增加在打开 webUI 首页时自动运行的选项。auto: 自动运行 click: 点击运行(默认)
  "note": "备注信息"
}]
```

name 对应值可以为任意字符。
logo 对应值为 img src 属性值,显示大小为 60x60,建议使用图片大小 180x180。可以是一个 http 链接,也可以是 efss 目录中的图片。当省略或加载失败时,将根据 hash 值自动生成 logo,具体的生成算法参考自 https://elecv2.github.io/#算法研究之通过字符串生成艺术图片
type 目前支持 **js efh shell url** 四种类型。 v3.7.0 增加 eval
target 为最终执行的内容。
hash 生成算法,md5(NAME + TYPE + TARGET)。

run  在打开 webUI 时,该 EAPP 的运行方式(v3.7.3)。共两个选择项 auto: 自动运行,click: 点击运行
- auto: 自动运行。每次打开 webUI 首页加载 EAPP 列表时,运行一次。不影响之后通过点击再次运行
- click: 手动点击运行(默认)

note 备注信息(v3.7.3 增加)。

### 执行

**JS 和 SHELL** 类型点击后,将发送一个 POST 请求到后台,执行对应脚本,然后运行日志通过 websocket 返回给前台。

**EFH 和 打开网址** 两个类型的应用将在浏览器的新标签页中打开。

**EVALRUN** 类型,将直接使用 **eval 函数** 在前端网页中执行对应代码。不支持使用文件,只能是纯 JS 原生代码,仅在前端页面中运行。

![EAPP 编辑](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/eapp_overview.png)

打开编辑模式后,再次点击图标可编辑 EAPP 内容,点击右上角的 X 按钮删除应用。

问题反馈 https://github.com/elecV2/elecV2P/issues

### 待添加

- 小组件
- 编辑脚本内容快捷

### 小组件 widget

在首页显示 widget 小工具

## 实现

EAPP 图标/LOGO 使用 canvas 显示,方便通过 JS 修改,可做成动态 widget

## 更新/trigger

- 每次打开首页是触发
- cron
- JS 内部控制

### elecV2P 全部说明文档

https://github.com/elecV2/elecV2P-dei/tree/master/docs

================================================
FILE: docs/dev_note/webhook token 权限设计.md
================================================
### 目的

限制某个 token 可访问的目录/时间/次数等。

### 实现

- 可设置多个 token

每个 token 对应的权限:(返回 info
- 完整权限(管理员
- 可访问路径。比如 限制某个 token 除 logs 外其他接口都不可访问
- 可访问时间。2021-07-20 至 2021-07-21 可精确到秒
- 可访问次数。访问几次后,该 token 自动失效
- 其他可能添加
  - 限制 IP

### 问题

相同 token 授于不同权限的?

1. 并集。提供对应 token 的所有权限
2. 禁止/覆盖。仅后条 token 对应权限生效(理论上不可设置相同 token

根据逻辑及方便性取第 **2** 种。但会产生问题,假如想限制某个 token 在某个时间段对某个路径的访问次数,第 2 种授权访问明显无法达到效果。所以还是得采用第 1 种吗?
再仔细想想,此时,可更改 token 授权内容,单个 token 可对应多个权限限制。这里会产生的问题: 如何在前端比较好的表现,让单个 token 可以无限的添加权限?

单 token 单权限可以直接使用一个表格 tr 行进行设置。那单个 token 多个权限呢? 在保持 token 单元格不动的情况下,增删授权行?

睡了一觉,想法有些改变。应该先设置好相关权限,然后生成相应的 token。甚至更进步一些,设置 token 和权限的对等函数,从 token 中可直接读取到对应权限。这样,在前端方面,可专门设置一个区域用于设置权限,然后生成 token,token 还是以表格的形式进行保存。

于是又产生了一个问题:token 生成函数如何编写/设置呢?得去学习参考 oAuth2.0 相关资料了。

2021-09-18

oAuth2.0 不可取,太 heavy 了。要设计另外的 token 模式,基本形式基于 uuid(比如: fcef12cf-6694-48bb-8568-63bd025cf5ad), 然后按位设计权限,部分使用 主 token 进行加密认证。比如 0x01 表示拥有访问路径的权限,0x02 表示限制访问时间,同时权限则为 0x03,依此类推。然后取 01/02/03 或加密后的两位于 uuid 固定位置中。

关于 uuid 的权限加密及具体协议待进一步仔细设计。

## Cookie 授权登录 2021-10-23 18:18

新增 cookie 授权验证。cookie 生成及验证方式:

``` JS
// 生成
btoa((wbtoken + wbtoken ).substr(iRandom(wbtoken.length), 8))

// 验证
let cookies = cookie.parse(req.headers.cookie || '')
(wbtoken + wbtoken).indexOf(atob(cookies.token)) !== -1
```

优点:

- 和 token 相关联(部分),切换 token 后 cookie 失效
- 计算简单,可快速检测 cookie 是否有效

缺点:

- 不够优雅?
- 如果原来的 token 太简单的话,可能会被碰撞出结果

**v3.7.4 之后 cookie 对应值调整为 userid(基于 webhook token 使用 md5 算法生成)**

## 临时多 TOKEN 设计

``` JSON
"tokens": {
  "md5hash(token)": {
    "enable": true,                     // 快捷开关
    "token": "9855d6cb-0c70-41d2-a246-54ebb365e9e3",
    "path": "/efss/temp|^/logs",        // 可访问路径。正则表达式字符串,匹配方式 new RegExp(path).test(req.path)。留空表示允许所有
    "note": "给 XX 的临时 TOKEN",       // 备注信息
  }
}
```

================================================
FILE: docs/dev_note/下载其他扩展程序.md
================================================
### 简单说明

直接下载其他可执行程序。

### 基础格式

``` JSON
[{
  "name": "程序名称",
  "resource": "url/to/download",
  "file_type": "zip | tar | exe",
  "install_path": "/path/to/install",
}]
```

================================================
FILE: docs/dev_note/关于引入区块链的可行性.md
================================================
```
初始发布: 2021-12-11 14:53
最近更新: 2022-09-20 11:53
```

### 目的

- 防止脚本被更改
- 脚本分布式存储
- 给予脚本作者一定奖励

### 基本架构

暂命名为 **EVP**,方便下面的说明,之后可能会修改。

可选方案:

1. 完全自定义,基于 node 开发
2. 基于 ETH/EOS 等,使用 solidity 开发

大概的数据结构:

- 脚本 hash 值(基于脚本内容
- 脚本名称
- 脚本类型(本地、远程
- 脚本地址
- 脚本内容
  - 是否加密?
  - 特定用户可查看?
- 脚本作者(可选
- 脚本说明(可选
- 脚本对应积分/代币地址
- 更新日期(自动添加
- 定时发布(可选
- 失效日期(可选
  - 自毁或不可见?

## 细节问题

- 如何查找脚本(脚本 md5 hash 值)
- 引用其他脚本
- 发布脚本花费 token(gas
  - 空投 token 给早期脚本开发者
  - 根据脚本大小确定 gas 费用

### 可能加入的功能

- 脚本修改记录(类似 Git commit
- 他人修改脚本(类似 PR
- 引入其他获取 Token 的方式
  - 传送数据/广播
  - 提供算力
    - 每个节点可提交要计算的问题
    - 每个节点初始化可提供的算力

- 脚本可引用 EVP 钱包,进行一些签名授权操作
- EDNS/.efh 域名,输入域名打开对应脚本
- 可选择实名制,方便找回
- 担保转账。转账给他人时引入第三方担保者

### 一些说明 2021-12-20 15:19

- 与 DAPP 的区别

目前本人对 DAPP 了解的不多,但 DAPP 好像是运行在云端(EVM),用户通过请求发送参数运行。而 EVP 只是保存代码用户下载后可在本地运行。因此 EVP 最终可能无法运行在 ETH 中,而不得不自己设计一套区块链系统?(希望不要如此

### 可能的模式

只在链上只保存脚本的 hash 值以及所有者,脚本内容还是按之前的方式保存,比如本地或远程服务器(github/自建服务器 等)。运行脚本时将脚本的 hash 值发送到 智能合约/DAPP 中,这一步会产生一个问题,发送指令到智能合约是需要消耗 gas 费的,每运行一次付费一次,显然不合理。可能要在这一步引入链下认证环节(所谓的 layer2/layer3 网络?这部分知识待学习)。

### 开发步骤 2022-02-24 20:42

1. 设计钱包 wallet

可引入公钥/私钥模式,但出于安全式考虑,私钥存储在服务器上可能会被其他脚本读取,所以私钥得再加密一次?
考虑双层私钥模式,一般操作可由第一层私钥授权完成,当涉及到代币/资金转移等问题时,提供一个交互界面,用户手动授权。类似于目前的 MetaMask 钱包。

钱包中显示自己开发的脚本,“已购买”的脚本。类似于 MeatMask 钱包 NFT 展览的概念。

引入 eth 开源钱包。(如果不复杂的话,考虑自己写一个。)

主要是只要包含原始密码(12 个单词),以及生成公钥即可,不需要链相关的开发,难度应该不大。

2. 智能合约

先在 ETH 链上进行开发,熟悉智能合约开发环境,也方便用户进行理解。

ETH 链上开发的优点:

- 相关的学习资料很多
- 开发者和用户也很多

缺点:

- 使用成本(gas 费用)较高(或许这个问题 ETH 之后会解决

等 ETH 链上的智能合约运行稳定后,再考虑开发原生的公链/私链,将 ETH 上的智能合约进行转移。(这部分可能需要一段很长很长的时间,3-5年?)

自己发公链/私链的优点:

- 自定义程度高(包含运行和费用等各方面
- 开发者/用户的学习成本增加

缺点:

- 开发难度高

3. 自建主链

主链名称 EVP, 发币为 evcoin (大写 EVCOIN)。

每个上链的内容为一个 JSON 数据,包含:
(此处应综合参考 telegram api 数据传输格式,bitcoin/ETH block 数据存储格式)

``` json
{
  "idx": 0,
  "hash": "",
  "from": {

  },
  "to": {

  },
  "data": {
    "type": "msg|nft|coin|script",
    "sign": "签名"
  }
}
```

早期活动:

- 在 elecV2P 首页随机领取 evcoin,相当于空投
- 引入 主题 NFT,随机空投


### 当前区块链的问题

- proof of work 的本质是在做无用功
- 太过于专注于技术而偏移了实际应用
- 每个节点都保存所有账本(这是必须,但或许可以进行选择性优化
  - 分层(当前所有的区块链都是无效的尝试? 2022-01-26 11:53
  - 中心化不可避免(选择可信任节点
- 可靠的数据结构大于所有区块链项目(?区块链的本质是一个动态的数据结构
  - 早期的电子/程序设备是不可更改的,动态可编程让互联网有了可能
  - 区块链不可更改,智能合约(动态可编程)让这项技术有了更进一步的发展
- 人人都可以发布主链,人人都可以给主链分层,人人都可以编写智能合约,中心化吗?


## 自定义区块链 2022-05-08 16:53

- 主链及分层
- 不需要 gas 调用合约
  - 由合约指定 gas 费用
- 中心化的区块链
- 用户不需要投票,反正他们也不会去知道投票内容,以及会产生的影响(待考虑
- 官方网站发布最终的 blockhash, 以及对应时间
- 引入零知识量证明机制。layer2,本地计算,提交结果及公钥到主链
- 创世块设置超级管理员(God),赋予超级管理员设置其他管理员的权限

- message is the money. 信息即金钱。转移/传递信息就是转移/传递金钱
- All Buffer, All binary?

**先做出来,不要考虑效率、内存、安全方面的问题。做出来->测试->修改->测试->修改->测试... 毕竟 what to lose?**

## NFT 相关设计  2022-09-20 11:49

种类:
- 脚本
- 主题
- 头像(包含生成头像的算法
- 任意文本(其他类型的程序、脚本)

基础结构:(待优化(可参考 git api info

``` JSON
{
  "name": "名称",
  "note": "一些相关说明",
  "owner": "所有者 id",     // 考虑显示为 Object,展示用户部分基础信息
  "type": "js",
  "data": "binary data",
  "size": "data size",
  "hash": "hash of data",
}
```

内部可包含函数,用于修改 NFT 的数据(类似于 contract)。这部分参考 wasm 实现。

NFT 可转移所有权(出售
NFT 可仅授权他人使用,但保留所有权。

授权内容:

- 费用(以 evcoin 结算,可为 0
- 时间(开始及结束

================================================
FILE: docs/dev_note/可能永不执行的长期计划.md
================================================
### 重命名

原因:现在的名称 elecV2P 不好发音,考虑取一个方便读写的名字。

备选:

- moefi
- moeku
- kumoe
- kufee

================================================
FILE: docs/dev_note/启动器快捷方式 $run.md
================================================
## 目的

用一个 .json 文件作为启动的快捷方式,方便将多个动作进行组合,及一键运行。

## 运行

应该包含的动作,或者说功能:

- 关键字: 对应功能

具体任务类
- script: 运行脚本
- shell: 执行 shell 指令
- task:  开始/暂停任务
- download:  下载文件(可能增加可选择下载方式 type git/wget/aria2c 等
- notify: 发送通知
- open:  直接打开一个 url 或 文件
- efh: 打开 efh 文件

执行逻辑类
- if:    如果逻辑 (每个任务执行后判断?
- next:  下一个任务(配合 if 使用
- done:  结束语句
- for:   for 循环逻辑
- while: while 循环逻辑
- wait:  等待
- aski:  ask for input 要求输入

## 格式 2021-12-16 18:06

(研究中,非最终版本

``` JSON
{
  "log": "path/to/my.log",                  // 启动后日志保存路径。可省略
  "name": "一个启动文件",
  "note": "关于该启动器的一些说明",
  "logo": "https://xxx.com/xxx.png",        // 对应图标
  "author": "作者 elecV2",
  "resouce": "https://xxxxxx/xxxx.json",    // 远程更新地址
  "actions": [     // 待执行的系列动作
    {
      "name": "任务一",
      "id": "taskone",           // 使用 id 方便跳转
      "type": "script",          // 可以使用远程脚本
      "args": ["test.js", "-grant", "nodejs"],
    }, {
      "name": "任务二",
      "type": "shell",
      "args": ["node", "-v"]
    }, {
      "name": "停止任务",
      "type": "task",
      "next": "anot",          // 使用 next + id 进行跳转
      "args": ["stop", "taskid"]
    }, {
      "name": "下载文件一",
      "type": "download",
      "args": ["https://resouce.url", "path/to/save", "type|wget|..."]
    }, {
      "name": "efh 测试",
      "type": "efh",
      "args": ["path/to/router", "type|wget|..."]
    }, {
      "id": "anot",
      "name": "发送通知",
      "type": "notify",
      "args": ["title", "body", "url", "bark|ifttt|cust|或省略"]
    }
  ],
  "mixif": "amixif.js",        // 每次任务完成后的判断脚本。可省略
}
```

说明:

mixif: 
使用某个脚本对每次执行的任务结果进行判断。传入如下几个参数:
- 任务结果  $env.acres
- 任务 id/或顺序  $env.acid
- 任务名称  $env.acname

返回执行结果
- 不作任何处理
- 跳转到一下任务 next
- 结束运行 done

### 最小单元测试

``` JS
function run({
  id, name,
  type, args,
}) {
  // some code
}
// run test.js
// run test.js -grant nodejs
// run ls -cwd efss
// run test.py
// run bash.sh
// task start tid
// download url/path
```

### 待解决的问题

- 获取上一个任务的执行结果
- 加入 IF/WHILE/FOR 等逻辑
- .JSON 文件的可视化编辑
- run bash 文件 process stdin

#### 非问题的问题

其实这些都可以在一个 JS 里面完成,所以有必要吗?

有。可以组合不同的脚本使用,方便进行整合。最主要是可以为以后的拖动/可视化编辑打好基础。

没有。纯属闲得蛋疼,会有人用这个吗?闲着没事做吗?纯属浪费时间。会获得什么吗?值得吗?

================================================
FILE: docs/dev_note/开发者激励计划.md
================================================
## 开发者激励计划

简单说明:给予 elecV2P 脚本开发者一定的现金奖励。

奖励金额:激励计划总金额 10000 元,发完即止。

活动截止时间:2022-06-30

奖励说明:单个脚本奖励 10-100 元不等,本次活动每个作者最高奖励 200 元,希望能把机会留给更多的人。欢迎下期继续参加。

**最终说明以 TG 频道 @elecV2 的通知为准**

### 具体实施

提交方式:

- Github 主动提交(推荐
  - https://github.com/elecV2/elecV2P-scripts
  - 提交格式参考仓库 readme 文档
- E-mail 发送脚本
  - elecV2#icloud.com (#->@)

提交内容:

- 脚本链接
- 脚本的简单说明
- 赞赏 QR 或者账号

可能问题:他人冒领(推荐使用 Github 账号直接 PR,如发送邮箱尽量附带一张后台截图。)

### 参考示例

elecV2P 默认自带的脚本:[elecV2P 默认脚本](https://github.com/elecV2/elecV2P/tree/master/script/JSFile)

JS 脚本编写参考:[说明文档 04-JS.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md)

推荐编写 efh 脚本。
文档参考:[efss.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/08-logger&efss.md) 中 efh 相关部分。
实例脚本:[efh 测试脚本](https://github.com/elecV2/elecV2P-dei/tree/master/examples/JSTEST/efh)

### 脚本限制

- 不可加密
- 长期可用(至少一个月内有效,可调整)
- 非 VIP 破解类
- 脚本内注明:兼容 elecV2P

### 获取奖励的时间

提交脚本的下一个周五前。比如这周星期一到星期日提交,下周五前将发送奖励现金到指定账号。

### 之后的计划:

- script_store.efh 应用中心

================================================
FILE: docs/dev_note/引入广告系统.md
================================================
### 目的

盈利。
让项目能更好的发展,以及支持**开发者激励计划**。
让在 elecV2P 编写脚本的开发者可以受益。脚本开发者有动力写脚本,用户就有更多的脚本可用,实现一个正向循环。

### 广告位

在 webUI 首页底部增加 2-3 个广告位。待实现其他盈利方式后,考虑完全取消。

### 价格

正常价位:

- 图片 1800 元/月
- 文字  500 元/月

测试阶段:

- 图片 600/月
- 文字 200/月

**测试阶段最多租用 3 个月,测试结束时间待定**

简单说明:广告是为了盈利及发展,如果实在不想看到,可考虑赞助开发者以进行屏蔽

### 联系方式

E-mail: elecV2#icloud.com (#->@)
Telegram: @elecv7

### 需要提供的内容

- 广告性质(非黄赌毒类
- 显示图片或文字
  - 图片要求:长 600-1000 高 40-60  推荐 844x50
- 落地页链接

### 可能增加的计划

- 提供广告接口给第三方开发者
- 广告显示给用户一定代币奖励

### 开发计划/要解决的问题

- 域名(很好解决
- 服务器(done
  - 广告数据的存储形式(文字、图片(重点
- 数据统计(以 cloud flare 为准

### 数据结构

广告数据结构

``` JSON
{
  "adid": "广告 ID",
  "link": "url/to/ad",
  "type": "txt|pic",
  "text": "显示/说明文字",
  "show": "url/to/show.png",
  "note": "更详细的说明",
  "rand": "出现概率",
  "position": "广告位",
  "sponsors": "广告商",
  "date_start": "开始时间",
  "date_end": "结束时间",
}
```

问题:

- [x] 多 sponsors 自动挑选
- [x] 展示时间开始|结果自动化
- [x] 部分数据只可展示给后台

sponsors 数据结构

```
{
  sid: {
    name: '名称',
    note: '留言',
    date: '日期',
    amount: '资金',
    channel: 'alipay',
    homepage: '主页',
    public: true
  }
}
```

================================================
FILE: docs/dev_note/待深度优化部分.md
================================================
### elecV2P 中还可以进行深度优化的地方

- runJSFile context 抽离共用的部分
- EFSS 文件列表 hash table or Map
- runJSFile cache JS 内容
- 脚本运行资源释放的问题
- 脚本运行停止 $done 的问题

### done

- rule/rewrite 正则匹配优化(hash (cache

================================================
FILE: docs/dev_note/根据 mitmhost 生成 pac 文件.md
================================================
### 说明

PAC 文件地址: webUI/pac 。 比如 http://127.0.0.1/pac 或者 https://xx.xxx(你的webUI地址)/pac

*[PAC 是什么?](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file)*

### 使用

在使用设备的代理设置部分,选择 PAC 自动代理,填写上面的 PAC 文件地址,保存即可。

### 功能

根据当前 mitmhost 列表自动生成 PAC 文件。

#### 提醒

- 如果 webUI 开启了安全访问,在填写 PAC 链接时注意带上 token,建议设置临时 token 进行访问。比如 http://192.168.1.101/pac?token=1234
- 如果更新 mitmhost 列表后 PAC 文件没有更新,建议在 PAC 链接后添加任意参数进行缓存更新。比如 http://192.168.1.1/pac?token=1234&update=154
- 如果设置 PAC 后无法联网,请确认代理地址及端口是否填写正确,以及 elecV2P 的 MITM 功能是否开启

================================================
FILE: docs/dev_note/脚本缓存_内容结果等.md
================================================
### 缓存结构

``` JS
const script_cache = new Map();
script_cache.set('script_name', {
  name: "name",
  hash: md5(code),
  time: "2022-04-17 21:25",
  type: "JS",
  grant: true,
  sudo: false,
  compatible: 'nodejs',
  // context: CONTEXT.final,
  code: "...",
  done: "res",
})

// 简版
let scache = {
  name: "name",
  hash: md5(code),
  time: "2022-04-17 21:25",
  code: "...",
  // 以下内容每次运行可能不一样,没有缓存的必要
  // type: "JS",
  // grant: true,
  // sudo: false,
  // compatible: 'nodejs',
  // context: CONTEXT.final,
  // done: "res",
}
```

### 目的、优点

- 加快脚本载入速度
- 加快脚本处理速度

### 问题

- 占用内存
- 部分资源无法释放

### 考虑添加

- 极速模式(不运行脚本,直接返回上次执行结果

================================================
FILE: docs/dev_note/节点互联.md
================================================
### 简介

不同的 elecV2P 服务器通过 websocket 连接。

### 形式

minishell connect userid

### 功能 - 连接后可以做什么

- 脚本传输

================================================
FILE: docs/dev_note/通过脚本管理规则 $rewrite.md
================================================
### 前期准备

class rewrite {
  list()
  add()
  remove()
  update()
  find()
}

### 基础使用

- $rewrite.list/add/remove/update/find

### 问题

- $rewrite 管理正在使用的规则还是规则文件?

================================================
FILE: docs/res/logo/readme.md
================================================
### elecV2P logo 文件夹

地址: https://github.com/elecV2/elecV2P-dei/tree/master/docs/res/logo

主色彩值: #003153

favicon-32x32.png

![favicon-32x32](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/logo/favicon-32x32.png)

elecV2P-180x180.png

![elecV2P-180x180](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/logo/elecV2P-180x180.png)

elecV2PBR-720x720.png

![elecV2PBR-720x720](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/logo/elecV2PBR-720x720.png)

elecV2Pall-720x720.png

![elecV2Pall-720x720](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/logo/elecV2Pall-720x720.png)

欢迎 PR 提交自行设计的 logo 图标

#### 其他说明

svg 文件使用 [Inkscape](http://www.inkscape.org/) 制作

================================================
FILE: examples/JS-elecV2P.sublime-build
================================================
// sublimet text build system 文件
// 功能:在 sublime 编辑器中使用 ctrl + B 快速运行测试脚本
// 使用:
//   - 复制本文件到 sublime xxxx\Data\Packages\User\ 文件夹下
//   - 然后根据 elecV2P 的具体运行,修改 cmd 命令中的 http 地址和 token 值,保存
//   - 然后在 sublime 的菜单栏选择 工具(tools)->Build System->JS-elecV2P
//   - 或者使用 ctrl+shift+b 快捷键切换默认 build system
//   
// 确保待运行 JS 文件位于 script/JSFile 目录,并且非子目录

{
  "cmd": "curl -s http://127.0.0.1:12521/webhook?token=a8c259b2-67fe-D-7bfdf1f5&type=runjs&fn=$file_name",
  // "cmd": "powershell curl '\"http://127.0.0.1/webhook?token=a8c259b2-67fe-D-7bfdf1f55cb3&type=runjs&fn=test/$file_name\"' | Select-Object -Expand Content"
}

================================================
FILE: examples/JSTEST/0body.js
================================================
$done({ response: { body: 'elecV2P body' }})

================================================
FILE: examples/JSTEST/TGbotonFavend.js
================================================
/**
 * 功能: elecV2P TGbot on favend
 * 参考修改自: https://github.com/elecV2/elecV2P-dei/blob/master/examples/TGbotonCFworker2.0.js
 * 最近更新: 2021-11-13
 * 更新地址: https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/TGbotonFavend.js
 * 问题反馈: https://github.com/elecV2/elecV2P-dei/issues
 * 
 * 与 CFworker 版相比较的优点:
 * - 支持使用 IP,无需域名
 * - 无需注册 cloudflare
 * 
 * 使用方式: 
 * 1. 准备工作
 *  - elecV2P 服务器外网可访问(测试: http://你的 elecV2P 服务器地址/webhook?token=你的webhook token&type=status )
 *  - 在 https://t.me/botfather 申请一个 TG BOT,记下 api token
 *
 * 2. 部署代码
 *  - 根据下面代码中 CONFIG_EV2P 的注释,填写好相关内容
 *  - 然后把修改后的 JS 文件上传到 elecV2P(也可以上传后再修改
 *  - 然后在 elecV2P EFSS 界面 favend 相关设置中设置 关键字 tgbot(可自行设置为其他) | 运行 JS | TGbotonFavend.js(修改后的脚本名)
 *  - 接着在浏览器中打开链接: https://api.telegram.org/bot(tgbot api token)/setWebhook?url=http://服务器地址/efss/tgbot?token=你的 webhook token(连接 TGbot 和 favend)
 *  - 最后,打开 TGbot 对话框,输入下面的相关指令(比如 status),测试 TGbot 是否部署成功
 *
 * 实现功能及相关指令: 
 * 查看 elecV2P 运行状态
 * status === /status
 *
 * 查看服务器相关信息
 * /info
 * /info debug
 * 
 * 删除 log 文件
 * /deletelog file === /deletelog file.js.log === /dellog file
 * /dellog all  ;删除使用 log 文件
 *
 * 查看 log 文件
 * /log                 ;进入日志查看模式
 * /log 文件名称
 *
 * 定时任务相关
 * /task                ;进入任务管理模式
 * /taskinfo all        ;获取所有任务信息
 * /taskinfo taskid     ;获取单个任务信息
 * /taskstart taskid    ;开始任务
 * /taskstop taskid     ;停止任务
 * /taskdel taskid      ;删除任务
 * /tasksave            ;保存当前任务列表
 * 
 * 脚本相关
 * /runjs               ;进入脚本运行模式
 * /runjs file.js       ;运行脚本
 * /runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js
 * ;运行远程脚本同时重命名保存为 anotify.js
 * /runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/feed.js anotify.js
 * /deljs file.js       ;删除脚本
 *
 * shell 指令相关
 * /shell               ;进入 shell 指令模式
 * /exec ls  ===  /shell ls  ===  exec ls
 * exec pm2 ls
 * 
 * bot commands 2.0
runjs - 运行 JS
task - 任务管理模式
status - 内存使用状态
shell - shell 命令执行模式
store - store/cookie 管理
tasksave - 保存任务列表
log - 查看日志文件
context - 查看当前执行环境
end - 退出当前执行环境
info - 查看服务器信息
command - 列出所有指令

 * 更新方式: 
 * - 如果在 CONFIG_EV2P 中设置了 store,直接覆盖该脚本即可
 * - 如果没有设置 store,则复制覆盖除了开头的 CONFIG_EV2P 外其他所有内容到
 *
 * 适用版本: elecV2P v3.3.6 (低版本下部分指令可能无法正常处理)
 *
 * 待实现功能:
 * - 普通 Get 请求配置 UI
 * - 结合 favend 优化相关逻辑
**/

let CONFIG_EV2P = {
  name: 'elecV2P',              // bot 名称。可省略
  store: 'elecV2PBot_CONFIG',   // 常量储存 CONFIG_EV2P 配置。建议调试时留空,调试完成后再设置回 'elecV2PBot_CONFIG' )
  storeforce: false,     // true: 使用当前设置强制覆盖常量储存中的数据,false: 常量储存中有配置相关数据则读取,没有则使用当前设置运行并保存
  url: '/',    // elecV2P 服务器地址(非 80 端口填写 http://你的 elecV2P 服务器地址)
  wbrtoken: 'xxxxxx-xxxxxxxxxxxx-xxxx',      // elecV2P 服务器 webhook token(在 webUI->SETTING 界面查看)
  token: 'xxxxxxxx:xxxxxxxxxxxxxxxxxxx',     // telegram bot api token
  userid: [],            // 只对该列表中的 userid 发出的指令进行回应。默认: 回应所有用户的指令(高风险!)
  slice: -1200,          // 截取部分返回结果的最后 1200 个字符,以防太长无法传输(可自行修改)
  shell: {
    timeout: 1000*6,     // shell exec 超时时间,单位: ms
    contexttimeout: 1000*60*5,               // shell 模式自动退出时间,单位: ms
  },
  timeout: 5000,         // runjs 请求超时时间,以防脚本运行时间过长,无回应导致反复请求,bot 被卡死
  mycommand: {           // 自定义快捷命令,比如 restart: 'exec pm2 restart elecV2P'
    rtest: '/runjs test.js',    // 表示当输入命令 /rtest 或 rtest 时会自动替换成命令 '/runjs test.js' 运行 JS 脚本 test.js
    execls: 'exec ls -al',      // 同上,表示自动将命令 /execls 替换成 exec ls -al。 其他命令可参考自行添加
    update: {                   // 当为 object 类型时,note 表示备注显示信息, command 表示实际执行命令
      note: '软更新升级',
      command: 'runjs https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/softupdate.js'
    }
  },
  mode: {
    storemanage: false,         // 是否开启 store/cookie 管理模式。false: 不开启(默认),true: 开启
  }
}

let modenv = 'auto'    // 运行环境。默认为 auto,可选 'worker' || 'favend'

if (modenv !== 'worker' && modenv !== 'favend') {
  // 自动判断当前运行环境
  modenv = typeof $axios === 'undefined' ? 'worker' : 'favend'
}
const kvname = modenv === 'favend' ? $store : elecV2P  // elecV2P 为在 cf 上创建并绑定的 kv namespace
console.log('TGbot start on mode', modenv)

/************ 后面部分为主运行代码,若没有特殊情况,无需改动 ****************/

/************ 简易的转化为 elecV2P favend 可用模式 ************/
if (typeof fetch === 'undefined') {
  function fetch(url) {
    return $axios(url).then(res=>({
      text(){
        return typeof res.data === 'string' ? res.data : JSON.stringify(res.data, null, 2)
      },
      json(){
        return res.data
      }
    }))
  }
}

if (typeof Request === 'undefined') {
  function Request(url, init = {}) {
    return {
      ...init, url
    }
  }
}

if (typeof Response === 'undefined') {
  function Response(body, header) {
    return $done({
      response: {
        status: 200,
        header, body
      }
    })
  }
}

const store = {
  put: async (key, value)=>{
    if (modenv === 'favend') {
      return kvname.put(value, key)
    }
    return await kvname.put(key, value)
  },
  get: async (key, type)=>{
    if (modenv === 'favend') {
      if (type == 'json') {
        type = 'object'
      }
    }
    return await kvname.get(key, type)
  },
  delete: async (key)=>{
    await kvname.delete(key)
  }
}

const context = {
  get: async (uid) => {
    return await store.get(uid, 'json')
  },
  put: async (uid, uenv, command) => {
    let ctx = await context.get(uid)
    if (ctx === null || typeof ctx !== 'object') {
      ctx = {
        command: []
      }
    }
    if (uenv) {
      ctx.context = uenv
    }
    if (command) {
      ctx.command ? ctx.command.push(command) : ctx.command = [command]
    }
    ctx.active = Date.now()
    await store.put(uid, JSON.stringify(ctx))
  },
  end: async (uid) => {
    await store.put(uid, JSON.stringify({}))
  }
}

function surlName(url) {
  if (!url) {
    return ''
  }
  let name = ''
  let sdurl = url.split(/\/|\?|#/)
  while (name === '' && sdurl.length) {
    name = sdurl.pop()
  }
  return name
}

function timeoutPromise({ timeout = CONFIG_EV2P.timeout || 5000, fn }) {
  if (!/\.js$/.test(fn)) {
    fn += '.js'
  }
  return new Promise(resolve => setTimeout(resolve, timeout, '请求超时 ' + timeout + ' ms,相关请求应该已发送至 elecV2P,这里提前返回结果,以免发送重复请求' + `${fn ? ('\n\n运行日志: ' + CONFIG_EV2P.url + 'logs/' + surlName(fn) + '.log') : '' }`))
}

function getLogs(s){
  if (s !== 'all' && !/\.log$/.test(s)) {
    s = s + '.js.log'
  }
  return new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=getlog&fn=' + s).then(res=>res.text()).then(r=>{
      resolve(s === 'all' ? r : r.slice(CONFIG_EV2P.slice))
    }).catch(e=>{
      reject(e)
    })
  })
}

function delLogs(logn) {
  return new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=deletelog&fn=' + logn).then(res=>res.text()).then(r=>{
      resolve(r)
    }).catch(e=>{
      reject(e)
    })
  })
}

function getStatus() {
  return new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?type=status&token=' + CONFIG_EV2P.wbrtoken).then(res=>res.text()).then(r=>{
      resolve(r)
    }).catch(e=>{
      reject(e)
    })
  })
}

function getInfo(debug) {
  return fetch(CONFIG_EV2P.url + 'webhook?type=info&token=' + CONFIG_EV2P.wbrtoken + (debug ? '&debug=true' : '')).then(res=>res.text())
}

function getTaskinfo(tid) {
  tid = tid.replace(/^\//, '')
  return fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=taskinfo&tid=' + tid).then(res=>res.text())
}

function opTask(tid, op) {
  if (!/start|stop|del|delete/.test(op)) {
    return 'unknow operation' + op
  }
  tid = tid.replace(/^\//, '')
  if (/^\/?stop/.test(tid)) {
    op = 'stop'
    tid = tid.replace(/^\/?stop/, '')
  }
  return fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=task' + op + '&tid=' + tid).then(res=>res.text())
}

function saveTask() {
  return fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=tasksave').then(res=>res.text())
}

function taskNew(taskinfo) {
  // 新建任务
  if (!taskinfo) {
    return '没有任何任务信息'
  }
  let finfo = taskinfo.split(/\r|\n/)
  if (finfo.length < 2) {
    return '任务信息输入有误 '
  }
  taskinfo = {
    name: finfo[2] || '新的任务' + Math.ceil(Math.random()*100),
    type: finfo[0].split(' ').length > 4 ? 'cron' : 'schedule',
    time: finfo[0],
    job: {
      type: finfo[3] || 'runjs',
      target: finfo[1],
    },
    running: finfo[4] !== 'false'
  }
  return fetch(CONFIG_EV2P.url + 'webhook', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        token: CONFIG_EV2P.wbrtoken,
        type: 'taskadd',
        task: taskinfo
      })
    }).then(res=>res.text())
}

function jsRun(fn) {
  // 支持参数运行,参考说明文档 06-task.md 运行 JS 相关部分(elecV2P 需大于 v3.6.0
  return Promise.race([new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=runjs&fn=' + encodeURI(fn)).then(res=>res.text()).then(r=>{
      resolve(r)
    }).catch(e=>{
      reject(e)
    })
  }), timeoutPromise({ fn })])
}

function getJsLists() {
  return new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=jslist').then(res=>res.json()).then(r=>{
      resolve(r.rescode === 0 ? r.resdata : r)
    }).catch(e=>{
      reject(e)
    })
  })
}

function deleteJS(name) {
  return fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=deletejs&fn=' + name).then(res=>res.text())
}

function shellRun(command) {
  if (command) {
    command = encodeURI(command)
  } else {
    return '请输入 command 指令,比如: ls'
  }
  return fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + `&type=shell&timeout=${CONFIG_EV2P.shell && CONFIG_EV2P.shell.timeout || 3000}&command=` + command).then(res=>res.text())
}

function storeManage(keyvt) {
  if (!keyvt) {
    return '请输入要获取的 cookie/store 相关的 key 值'
  }

  let keys = keyvt.split(' ')
  if (keys.length === 1) {
    return new Promise((resolve,reject)=>{
      fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + `&type=store&key=${keyvt}`).then(res=>res.text()).then(r=>{
        if (r) {
          resolve(r.slice(CONFIG_EV2P.slice))
        } else {
          resolve(keyvt + ' 暂不存在')
        }
      }).catch(e=>{
        reject(e)
      })
    })
  } else {
    let body = {
      token: CONFIG_EV2P.wbrtoken,
      type: 'store'
    }
    if (keys[0] === 'delete') {
      body.op = 'delete'
      body.key = keys[1]
    } else {
      body.op = 'put'
      body.key = keys[0]
      body.value = decodeURI(keys[1])
      body.options = {
        type: keys[2]
      }
    }
    return fetch(CONFIG_EV2P.url + 'webhook', {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(body)
      }).then(res=>res.text())
  }
}

function storeList() {
  return new Promise((resolve,reject)=>{
    fetch(CONFIG_EV2P.url + 'webhook?token=' + CONFIG_EV2P.wbrtoken + '&type=store&op=all').then(res=>res.json()).then(r=>{
      resolve(r.rescode === 0 ? r.resdata : r)
    }).catch(e=>{
      reject(e)
    })
  })
}

function getFile(file_id) {
  return new Promise((resolve,reject)=>{
    fetch(`https://api.telegram.org/bot${CONFIG_EV2P.token}/getFile?file_id=${file_id}`).then(res=>res.json()).then(r=>{
      if (r.ok) {
        resolve(`https://api.telegram.org/file/bot${CONFIG_EV2P.token}/${r.result.file_path}`)
      } else {
        resolve(r.description)
      }
    }).catch(e=>{
      reject(e)
    })
  })
}

async function handlePostRequest(request) {
  // console.log(request.body)
  if (CONFIG_EV2P.store) {
    let config = await store.get(CONFIG_EV2P.store, 'json')
    if (!CONFIG_EV2P.storeforce && config) {
      Object.assign(CONFIG_EV2P, config)
    } else {
      await store.put(CONFIG_EV2P.store, JSON.stringify(CONFIG_EV2P))
    }
  }
  if (!CONFIG_EV2P.url.endsWith('/')) {
    CONFIG_EV2P.url = CONFIG_EV2P.url + '/'
  }
  CONFIG_EV2P.timeout = CONFIG_EV2P.timeout || 5000

  let bodyString = await readRequestBody(request)
  let payload = {
    "method": "sendMessage",
    "chat_id": CONFIG_EV2P.userid[0],
    "parse_mode": "html",
    "disable_web_page_preview": true,
  }

  try {
    let body = typeof bodyString === 'string' ? JSON.parse(bodyString) : bodyString
    if (!body.message) {
      payload.text = 'elecV2P bot get unknow message:\n' + bodyString
      await tgPush(payload)
      return new Response("OK")
    }
    payload["chat_id"] = body.message.chat.id
    if (body.message.document) {
      let bodydoc = body.message.document
      payload.text = `文件名称: ${bodydoc.file_name}\n文件类型: ${bodydoc.mime_type}\n文件 id: ${bodydoc.file_id}\n`
      let fpath = await getFile(bodydoc.file_id)
      payload.text += `文件地址: ${fpath}\n\n(进一步功能待完成)`
      await tgPush(payload)
      return new Response("OK")
    }
    if (body.message.text) {
      let bodytext = body.message.text.trim()
      let uid = 'u' + payload['chat_id']

      if (CONFIG_EV2P.mycommand && Object.keys(CONFIG_EV2P.mycommand).length) {
        let tcom = bodytext.replace(/^\//, '')
        if (CONFIG_EV2P.mycommand[tcom]) {
          bodytext = CONFIG_EV2P.mycommand[tcom].command || CONFIG_EV2P.mycommand[tcom]
        }
      }
      if (bodytext === 'sudo clear') {
        await store.delete(uid)
        payload.text = 'current context is cleared.'
        await tgPush(payload)
        return new Response("OK")
      } else if (bodytext === '/command') {
        payload.text = `/runjs - 运行 JS
/task - 任务管理模式
/status - 内存使用状态
/shell - shell 指令执行模式
/store - store/cookie 管理
/tasksave - 保存任务列表
/taskdel + tid - 删除任务
/deljs + JS 文件名 - 删除 JS
/log - 获取日志
/dellog + 日志名 - 删除日志
/context - 查看当前执行环境
/end - 退出当前执行环境
/info - 查看服务器信息
/command - 列出所有指令`

        if (CONFIG_EV2P.mycommand && Object.keys(CONFIG_EV2P.mycommand).length) {
          payload.text += '\n\n自定义快捷命令'
          for (let x in CONFIG_EV2P.mycommand) {
            payload.text += '\n' + (x.startsWith('/') ? '' : '/') + x + ' - ' + (CONFIG_EV2P.mycommand[x].note || CONFIG_EV2P.mycommand[x])
          }
        }
        await tgPush(payload)
        return new Response("OK")
      }
      let userenv = await context.get(uid)
      
      if (CONFIG_EV2P.userid && CONFIG_EV2P.userid.length && CONFIG_EV2P.userid.indexOf(body.message.chat.id) === -1) {
        payload.text = "这是 " + CONFIG_EV2P.name + " 私人 bot,不接受其他人的指令。\n如果有兴趣可以自己搭建一个: https://github.com/elecV2/elecV2P-dei\n\n频道: @elecV2 | 交流群: @elecV2G"
        await tgPush({
          ...payload,
          "chat_id": CONFIG_EV2P.userid[0],
          "text": `用户: ${body.message.chat.username},ID: ${body.message.chat.id} 正在连接 elecV2P bot,发出指令为: ${bodytext}`
        })
      } else if (/^\/?end/.test(bodytext)) {
        await context.end(uid)
        payload.text = `退出上文执行环境${(userenv && userenv.context) || ''},回到普通模式`
      } else if (/^\/?context$/.test(bodytext)) {
        if (userenv && userenv.context) {
          payload.text = '当前执行环境为: ' + userenv.context + '\n输入 /end 回到普通模式'
        } else {
          payload.text = '当前执行环境为: 普通模式'
        }
      } else if (/^\/?status/.test(bodytext)) {
        payload.text = await getStatus()
      } else if (/^\/?info/.test(bodytext)) {
        let cont = bodytext.trim().split(' ')
        if (cont.length === 1) {
          payload.text = await getInfo()
        } else if (cont.pop() === 'debug') {
          payload.text = await getInfo('debug')
        } else {
          payload.text = 'unknow info command'
        }
      } else if (/^\/?(dellog|deletelog) /.test(bodytext)) {
        let cont = bodytext.replace(/^\/?(dellog|deletelog) /, '')
        if (!(cont === 'all' || /\.log$/.test(cont))) cont = cont + '.js.log'
        payload.text = await delLogs(cont)
      } else if (/^\/?taskinfo/.test(bodytext)) {
        let cont = bodytext.replace(/^\/?taskinfo/, '')
        payload.text = await getTaskinfo(cont.trim() || 'all')
      } else if (/^\/?taskstart /.test(bodytext)) {
        let cont = bodytext.replace(/^\/?taskstart /, '')
        payload.text = await opTask(cont, 'start')
      } else if (/^\/?taskstop /.test(bodytext)) {
        let cont = bodytext.replace(/^\/?taskstop /, '')
        payload.text = await opTask(cont, 'stop')
      } else if (/^\/?taskdel /.test(bodytext)) {
        let cont = bodytext.replace(/^\/?taskdel /, '')
        payload.text = await opTask(cont, 'del')
      } else if (/^\/?tasksave/.test(bodytext)) {
        payload.text = await saveTask()
      } else if (/^\/?deljs /.test(bodytext)) {
        let cont = bodytext.replace(/^\/?deljs /, '')
        payload.text = await deleteJS(cont)
      } else if (/^\/?task/.test(bodytext)) {
        let cont = bodytext.trim().split(' ')
        if (cont.length === 1) {
          try {
            await context.put('u' + payload['chat_id'], 'task')
            let tasklists = await getTaskinfo('all')
            let tlist = JSON.parse(tasklists)
            let tlstr = []
            for (let tid in tlist.info) {
              tlstr.push(`${tlist.info[tid].running ? '🐢' : '🐰'} ${tlist.info[tid].name} /${tid}  |  /stop${tid}`)
              if (tlstr.length > 80) {
                payload.text = tlstr.join('\n')
                await tgPush(payload)
                tlstr = []
              }
            }

            payload.text = `\n${tlstr.join('\n')}\n当前 elecV2P 定时任务共 ${tlist.total} 个,运行中(🐢)的任务 ${tlist.running} 个\n点击任务名后面的 /+tid 开始任务,/+stoptid 停止任务\n也可以手动输入对应的 tid 开始任务, stop tid 停止任务\ntaskinfo tid 查看任务信息`
            await tgPush(payload)

            payload.text = `按照下面格式多行输入可直接添加新的任务(每行表示一个任务参数)\n
任务时间(cron 定时,比如: 8 0,8 * * * ,倒计时,比如: 1 10 6)
任务目标(test.js,node -v, LOlxkcdI(某个任务的 tid),远程 JS 链接等)
任务名称(可省略,默认为 新的任务+随机参数)
任务类型(可省略,默认为 运行 JS,shell: 运行 shell 指令,taskstart:开始其他任务,taskstop:停止其他任务)
是否执行(可省略,默认为 true,当且仅当该值为 false 时,表示只添加任务信息而不运行)

示例一:添加一个 cron 定时任务

30 20 * * *
https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/deletelog.js
删除日志

示例二:添加一个倒计时任务,运行 test.js,每次倒计时 1 秒,执行 3 次

1 3
test.js`
          } catch(e) {
            payload.text = e.message
          }
        } else {
          payload.text = 'unknow task operation'
        }
      } else if (/^\/?runjs/.test(bodytext)) {
        let cont = bodytext.trim().split(/ +/)
        if (cont.length === 1) {
          try {
            await context.put('u' + payload['chat_id'], 'runjs')
            let jslists = await getJsLists()
            let keyb = {
              keyboard: [],
              resize_keyboard: false,
              one_time_keyboard: true,
              selective: true
            }
            let over = ''
            if (jslists.length >= 200) {
              over = '\n\n文件数超过 200,以防 reply_keyboard 过长 TG 无返回,剩余 JS 以文字形式返回\n\n'
            }
            for (let ind in jslists) {
              let s = jslists[ind]
              if (ind >= 200) {
                over += s + '  '
                continue
              }

              let row = parseInt(ind/2)
              keyb.keyboard[row]
              ? keyb.keyboard[row].push({
                text: s.replace(/\.js$/, '')
              })
              : keyb.keyboard[row] = [{
                text: s.replace(/\.js$/, '')
              }]
            }
            payload.text = '进入 RUNJS 模式,当前 elecV2P 上 JS 文件数: ' + jslists.length + '\n点击交互键盘可直接运行 JS,也可以输入文件名或者远程链接运行其他脚本\n后面可附带 -env/-rename 等参数(v3.6.0),比如\nhttps://远程JSname.js -rename=t.js' + over.trimRight()
            payload.reply_markup = keyb
          } catch(e) {
            payload.text = e.message
          }
        } else {
          payload.text = await jsRun(bodytext.replace(/^\/?runjs /, ''))
        }
      } else if (/^\/?(shell|exec)/.test(bodytext)) {
        let cont = bodytext.trim().split(' ')
        if (cont.length === 1) {
          try {
            await context.put('u' + payload['chat_id'], 'shell')
            let keyb = {
              keyboard: [
                [{text: 'ls'}, {text: 'node -v'}],
                [{text: 'apk add python3 ffmpeg'}],
                [{text: 'python3 -V'}, {text: 'pm2 ls'}]
              ],
              resize_keyboard: false,
              one_time_keyboard: true,
              selective: true
            }
            payload.text = '进入 SHELL 模式,可执行简单 shell 指令,比如: ls, node -v 等'
            payload.reply_markup = keyb
          } catch(e) {
            payload.text = e.message
          }
        } else {
          payload.text = await shellRun(bodytext.replace(/^\/?(shell|exec) /, ''))
        }
      } else if (/^\/?store/.test(bodytext)) {
        if (CONFIG_EV2P.mode && CONFIG_EV2P.mode.storemanage) {
          let cont = bodytext.trim().split(' ')
          if (cont.length === 1) {
            try {
              await context.put('u' + payload['chat_id'], 'store')
              let storelists = await storeList()
              let keyb = {
                keyboard: [],
                resize_keyboard: false,
                one_time_keyboard: true,
                selective: true
              }
              let over = ''
              if (storelists.length >= 200) {
                over = '\n\nCookie 数超过 200,以防 reply_keyboard 过长 TG 无返回,剩余 Cookie KEY 以文字形式返回\n\n'
              }
              for (let ind in storelists) {
                let s = storelists[ind]
                if (ind >= 200) {
                  over += s + '  '
                  continue
                }

                let row = parseInt(ind/2)
                keyb.keyboard[row]
                ? keyb.keyboard[row].push({
                  text: s
                })
                : keyb.keyboard[row] = [{
                  text: s
                }]
              }
              payload.reply_markup = keyb
              payload.text = '进入 cookie/store 管理模式,当前 elecV2P 上 Cookie 数: ' + storelists.length + '\n\n点击或者直接输入关键字(key)查看 store 内容,比如 cookieKEY\n\n输入 delete key 删除某个 Cookie。比如: delete cookieKEY\n\n输入 key value type(可省略) 修改 store 内容(以空格进行分隔)。如果 value 中包含空格等其他特殊字符,请先使用 encodeURI 函数进行转换。比如:\n\nCookieJD pt_pin=xxx;%20pt_key=app_xxxxxxx;\n\ntype 可省略,也可设定为:\nstring 表示将 value 保存为普通字符(默认)\nobject 表示将 value 保存为 json 格式\na 表示在原来的值上新增。(更多说明可参考 https://github.com/elecV2/elecV2P-dei/tree/master/docs/04-JS.md $store 部分)' + over
            } catch(e) {
              payload.text = e.message
            }
          } else {
            payload.text = await storeManage(bodytext.replace(/^\/?store /, ''))
          }
        } else {
          payload.text = 'store/cookie 管理模式处于关闭状态'
        }
      } else if (/^\/?log/.test(bodytext)) {
        let cont = bodytext.trim().split(' ')
        if (cont.length === 1) {
          try {
            await context.put('u' + payload['chat_id'], 'log')
            let res = await getLogs('all')
            let map = JSON.parse(res)
            let keyb = {
                  inline_keyboard: [ ],
                }

            map.forEach((s, ind)=> {
              let row = parseInt(ind/2)
              keyb.inline_keyboard[row]
              ? keyb.inline_keyboard[row].push({
                text: s.replace(/\.js\.log$/g, ''),
                url: CONFIG_EV2P.url + 'logs/' + s
              }) 
              : keyb.inline_keyboard[row] = [{
                text: s.replace(/\.js\.log$/g, ''),
                url: CONFIG_EV2P.url + 'logs/' + s
              }]
            })
            payload.text = "开始日志查看模式,当前 elecV2P 上日志文件数: " + map.length + "\n点击查看日志或者直接输入 log 文件名进行查看"
            payload.reply_markup = keyb
          } catch(e) {
            payload.text = e.message
          }
        } else {
          payload.text = await getLogs(bodytext.replace(/^\/?log /, ''))
        }
      } else if (userenv && userenv.context) {
        switch (userenv.context) {
          case 'log':
            payload.text = await getLogs(bodytext)
            break
          case 'runjs':
            payload.text = await jsRun(bodytext)
            break
          case 'task':
            if (bodytext.trim().split(/\r|\n/).length > 1) {
              payload.text = await taskNew(bodytext)
            } else {
              payload.text = await opTask(bodytext.split(' ').pop(), /^(🐢|\/?stop)/.test(bodytext) ? 'stop' : 'start')
            }
            break
          case 'shell':
            if (Date.now() - userenv.active > (CONFIG_EV2P.shell && CONFIG_EV2P.shell.contexttimeout)) {
              payload.text = '已经超过 ' + CONFIG_EV2P.shell.contexttimeout/1000/60 + ' 分钟没有执行 shell 指令,自动退出 shell 模式\n使用 /shell 命令重新进入\n/end 回到普通模式\n/command 查看所有指令'
              payload.reply_markup = JSON.stringify({
                remove_keyboard: true
              })
              userenv.context = 'normal'
            } else {
              payload.text = await shellRun(bodytext)
            }
            break
          case 'store':
            if (CONFIG_EV2P.mode && CONFIG_EV2P.mode.storemanage) {
              payload.text = await storeManage(bodytext)
            } else {
              payload.text = 'store/cookie 管理模式处于关闭状态'
            }
            break
          default: {
            payload.text = '当前执行环境: ' + userenv.context + ' 无法处理指令: ' + bodytext
          }
        }
        await context.put(uid, userenv.context, bodytext)
      } else {
        payload.text = 'TGbot 部署成功,可以使用相关指令和 elecV2P 服务器进行交互了\nPowered By: https://github.com/elecV2/elecV2P\n\n频道: @elecV2 | 交流群: @elecV2G'
        if (CONFIG_EV2P.userid.length === 0) {
          payload.text += '\n(❗️危险⚠️)当前 elecV2P bot 并没有设置 userid,所有人可进行交互'
        }
        if (bodytext === '/start') {
          let status = ''
          try {
            status = await getStatus()
            status = '当前 bot 与 elecV2P 连接成功 ' + status
          } catch(e) {
            status = (e.message || e) + '\nelecV2P 服务器没有响应,请检查服务器地址和 webhook token 是否设置正确。'
          }
          payload.text += '\n' + status
        }
      }

      await tgPush(payload)
      return new Response("OK")
    }
    return new Response(JSON.stringify(body), {
      headers: { 'content-type': 'application/json' },
    })
  } catch(e) {
    console.error(e)
    console.log('payload', payload)
    payload.text = e.message || e
    await tgPush(payload)
    return new Response("OK")
  }
}

async function handleRequest(request) {
  let retBody = `welcome to elecV2P Bot\n\n请根据 https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/examples/JSTEST/TGbotonFavend.js 中的注释进行配置\n\nPowered By: https://github.com/elecV2/elecV2P\n\nTG 频道: https://t.me/elecV2 | TG 交流群: https://t.me/elecV2G`
  return new Response(retBody)
}

if (modenv === 'worker') {
  addEventListener('fetch', event => {
    const { request } = event
    // const { url } = request
    if (request.method === 'POST') {
      return event.respondWith(handlePostRequest(request))
    } else if (request.method === 'GET') {
      return event.respondWith(handleRequest(request))
    }
  })
} else {
  if ($request.method === 'GET') {
    handleRequest()
  } else {
    handlePostRequest($request)
  }
}

/**
 * readRequestBody reads in the incoming request body
 * Use await readRequestBody(..) in an async function to get the string
 * @param {Request} request the incoming request to read from
 */
async function readRequestBody(request) {
  if (modenv === 'favend') {
    return request.body
  }
  const { headers } = request
  const contentType = headers['Content-Type'] || headers.get('content-type')
  if (contentType.includes('application/json')) {
    const body = await request.json()
    return JSON.stringify(body)
  } else if (contentType.includes('application/text')) {
    const body = await request.text()
    return body
  } else if (contentType.includes('text/html')) {
    const body = await request.text()
    return body
  } else if (contentType.includes('form')) {
    const formData = await request.formData()
    let body = {}
    for (let entry of formData.entries()) {
      body[entry[0]] = entry[1]
    }
    return JSON.stringify(body)
  } else {
    let myBlob = await request.blob()
    var objectURL = URL.createObjectURL(myBlob)
    return objectURL
  }
}

async function tgPush(payload) {
  const myInit = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json;charset=UTF-8'
    }
  };
  let maxbLength = 1200;
  if (payload.text && payload.text.length > maxbLength) {
    let reply_text = payload.text
    let pieces = Math.ceil(reply_text.length / maxbLength);
    for (let i=0; i<pieces; i++) {
      payload.text = reply_text.slice(i*maxbLength, (i+1)*maxbLength);
      myInit.body = JSON.stringify(payload)
      let myRequest = new Request(`https://api.telegram.org/bot${CONFIG_EV2P.token}/`, myInit);
      await fetch(myRequest);
      if (payload.reply_markup) {
        delete payload.reply_markup
      }
    }
  } else {
    myInit.body = JSON.stringify(payload);
    let myRequest = new Request(`https://api.telegram.org/bot${CONFIG_EV2P.token}/`, myInit);
    await fetch(myRequest);
  }
}

================================================
FILE: examples/JSTEST/aria2-env.js
================================================
// 需提前配置好 aria2 使用环境, 使命令 aria2c 在系统 Shell 环境中可用

// task runjs example:
// 运行 JS: aria2-env.js -e dlink=https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/webhook.js

let dlink = 'https://raw.githubusercontent.com/elecV2/elecV2P/master/script/JSFile/0body.js'
if (typeof($dlink) !== "undefined") {
  dlink = $dlink
}

$exec(`aria2c ${dlink} -d ${__efss}`, {
  call: true,
  cb(data, error, success){
    if (success) {
      console.log('aria2 download complete!', 'dlink:' + dlink)
      $feed.ifttt('aria2 download complete!', 'dlink:' + dlink, __home + '/efss')
    } else {
      error ? console.error(error) : console.log(data)
    }
  }
})

================================================
FILE: examples/JSTEST/asyncPool.js
================================================
/**
 * 异步并行执行函数及限制(待优化)
 * 已实现功能:
 * - 可在 cb 函数中动态添加参数
 * - 可在 cb 中提前结束运行
 * 待优化部分:
 * - 逻辑再写得清晰/简洁一点?
 * author     https://t.me/elecV2
 * update     https://github.com/elecV2/elecV2P-dei/blob/master/examples/JSTEST/asyncPool.js
 * @param     {Function}    fn       待执行的异步函数
 * @param     {Array}       params   函数传入参数
 * @param     {Function}    cb       回调函数
 * @param     {Number}      limit    同时并发执行数
 * @return    {Promise}
 */
function promisePool(fn, params, { cb, limit = 6, log = false }) {
  if (typeof(fn) !== 'function') {
    return Promise.reject('a function is expect')
  }
  if (!Array.isArray(params)) {
    return Promise.reject('a array of params is expect')
  }
  let cnlog = (...args)=>{
    if (log) {
      console.log.apply(null, args)
    }
  }
  let cback = async (options = {}) => {
    // callback 可能会加入新的 params
    if (typeof(cb) === 'function') {
      return await cb(options)
    } else {
      cnlog(options)
    }
  }
  let last  = 0, fail = [], cbdone = false
  let orbit = new Map()
  let isCbDone = (flag = false)=>{
    // call force done 只生效一次
    if (flag === 'done') {
      cbdone = true
    }
    return cbdone
  }
  let nTask = async (idx) => {
      let curt = last++, orbitdone = orbit.get(idx) || []
      await cback({ message: `orbit ${idx} start`, orbit: idx, running: curt, done: orbitdone })
      cnlog('orbit', idx, 'start task', curt)
      let res = null, tempdone = false
      try {
        res = await fn(params[curt])
        orbitdone.push(curt)
        orbit.set(idx, orbitdone)
        tempdone = await cback({ message: `task ${curt} finish`, orbit: idx, done: orbitdone, res })
      } catch(err) {
        console.error('task', curt, 'fail, data', params[curt])
        fail.push(curt)
        tempdone = await cback({ message: `task ${curt} fail with data ${params[curt]}`, orbit: idx, fail: err.message || err })
      }
      if (last >= params.length) {
        orbit.delete(idx)
        if (orbit.size === 0) {
          cnlog('all task done')
          await cback({ message: `total tasks ${last}, all finished`, finish: true, done: last, fail })
        }
      } else {
        if (isCbDone(tempdone)) {
          if (tempdone) {
            cnlog('force done by callback, fail', fail, 'current task', curt, 'with param', params[curt])
          }
          throw Error('force done by callback')
        } else {
          await nTask(idx)
        }
      }
    }

  return Promise.all(new Array(Math.min(limit, params.length)).fill(1).map((s, idx)=>nTask(idx)))
}

================================================
FILE: examples/JSTEST/boxjs.ev.js
================================================
// elecV2P v3.2.3 版本后,可直接使用 chavyleung 的原版 boxjs。本脚本不再维护
// 
// BoxJs elecV2P 兼容版。修改自:https://github.com/chavyleung/scripts/tree/master/box
// 简易修改,测试使用,不保证原 BoxJs 的所有功能能正常工作。
// 使用方法:
// - 在 webUI->REWRITE/重写请求 添加规则: ^http://boxjs\.com 网络请求前 https://raw.githubusercontent.com/chavyleung/scripts/master/box/chavy.boxjs.js
// - 然后将 boxjs.com 这个域名代理到 ANYPROXY 端口(确保端口已打开,默认为 127.0.0.1:8001)
// (如果使用 chrome 浏览器推荐使用 SwitchyOmega 插件来进行分流设置,也可以直接使用系统代理)
// - 最后浏览器打开 http://boxjs.com (http 访问无需安装证书,如果是 https 访问,在 MITM 页面下载安装证书)
// 
// 说明事项:
// - boxjs.com 可替换为任一域名,比如 e.com
// - 访问一个网址后浏览器会有缓存,如果首次测试失败,建议修改域名后再次尝试。(比如 e1.com/e2.cn/e3.org 等等)
// - 如果在 boxjs 中无法运行脚本,尝试在右上角菜单中清空 HTTP-API 内容,或者直接在左上角调整为 QuanX或Loon 模式

const $ = new Env('BoxJs')

// 为 eval 准备的上下文环境
const $eval_env = {}

$.version = '0.7.69'
$.versionType = 'beta'

// 发出的请求需要需要 Surge、QuanX 的 rewrite
$.isNeedRewrite = true

/**
 * ===================================
 * 持久化属性: BoxJs 自有的数据结构
 * ===================================
 */

// 存储`用户偏好`
$.KEY_usercfgs = 'chavy_boxjs_userCfgs'
// 存储`应用会话`
$.KEY_sessions = 'chavy_boxjs_sessions'
// 存储`页面缓存`
$.KEY_web_cache = 'chavy_boxjs_web_cache'
// 存储`应用订阅缓存`
$.KEY_app_subCaches = 'chavy_boxjs_app_subCaches'
// 存储`全局备份`
$.KEY_globalBaks = 'chavy_boxjs_globalBaks'
// 存储`当前会话` (配合切换会话, 记录当前切换到哪个会话)
$.KEY_cursessions = 'chavy_boxjs_cur_sessions'

/**
 * ===================================
 * 持久化属性: BoxJs 公开的数据结构
 * ===================================
 */

// 存储用户访问`BoxJs`时使用的域名
$.KEY_boxjs_host = 'boxjs_host'

// 请求响应体 (返回至页面的结果)
$.json = $.name // `接口`类请求的响应体
$.html = $.name // `页面`类请求的响应体

// 页面源码地址
$.web = `https://cdn.jsdelivr.net/gh/chavyleung/scripts@${$.version}/box/chavy.boxjs.html?_=${new Date().getTime()}`
// 版本说明地址 (Release Note)
$.ver = 'https://cdn.jsdelivr.net/gh/chavyleung/scripts@${$.version}/box/release/box.release.tf.json';

(async () => {
  // 勿扰模式
  $.isMute = [true, 'true'].includes($.getdata('@chavy_boxjs_userCfgs.isMute'))

  // 请求路径
  $.path = getPath($request.url)

  // 请求类型: GET
  $.isGet = $request.method === 'GET'
  // 请求类型: POST
  $.isPost = $request.method === 'POST'
  // 请求类型: OPTIONS
  $.isOptions = $request.method === 'OPTIONS'

  // 请求类型: page、api、query
  $.type = 'page'
  // 查询请求: /query/xxx
  $.isQuery = $.isGet && /^\/query\/.*?/.test($.path)
  // 接口请求: /api/xxx
  $.isApi = $.isPost && /^\/api\/.*?/.test($.path)
  // 页面请求: /xxx
  $.isPage = $.isGet && !$.isQuery && !$.isApi

  // 升级用户数据
  upgradeUserData()

  // 处理预检请求
  if ($.isOptions) {
    $.type = 'options'
    await handleOptions()
  }
  // 处理`页面`请求
  else if ($.isPage) {
    $.type = 'page'
    await handlePage()
  }
  // 处理`查询`请求
  else if ($.isQuery) {
    $.type = 'query'
    await handleQuery()
  }
  // 处理`接口`请求
  else if ($.isApi) {
    $.type = 'api'
    await handleApi()
  }
})()
  .catch((e) => $.logErr(e))
  .finally(() => doneBox())

/**
 * http://boxjs.com/ => `http://boxjs.com`
 * http://boxjs.com/app/jd => `http://boxjs.com`
 */
function getHost(url) {
  return url.slice(0, url.indexOf('/', 8))
}

/**
 * http://boxjs.com/ => ``
 * http://boxjs.com/api/getdata => `/api/getdata`
 */
function getPath(url) {
  // 如果以`/`结尾, 去掉最后一个`/`
  const end = url.lastIndexOf('/') === url.length - 1 ? -1 : undefined
  // slice第二个参数传 undefined 会直接截到最后
  // indexOf第二个参数用来跳过前面的 "https://"
  return url.slice(url.indexOf('/', 8), end)
}

/**
 * ===================================
 * 处理前端请求
 * ===================================
 */

/**
 * 处理`页面`请求
 */
async function handlePage() {
  // 获取 BoxJs 数据
  const boxdata = getBoxData()
  boxdata.syscfgs.isDebugMode = false

  // 调试模式: 是否每次都获取新的页面
  const isDebugWeb = [true, 'true'].includes($.getdata('@chavy_boxjs_userCfgs.isDebugWeb'))
  const debugger_web = $.getdata('@chavy_boxjs_userCfgs.debugger_web')
  const cache = $.getjson($.KEY_web_cache, null)

  // 如果没有开启调试模式,且当前版本与缓存版本一致,且直接取缓存
  if (!isDebugWeb && cache && cache.version === $.version) {
    $.html = cache.cache
  }
  // 如果开启了调试模式,并指定了 `debugger_web` 则从指定的地址获取页面
  else {
    if (isDebugWeb && debugger_web) {
      // 调试地址后面拼时间缀, 避免 GET 缓存
      const isQueryUrl = debugger_web.includes('?')
      $.web = `${debugger_web}${isQueryUrl ? '&' : '?'}_=${new Date().getTime()}`
      boxdata.syscfgs.isDebugMode = true
      console.log(`[WARN] 调试模式: $.web = : ${$.web}`)
    }
    // 如果调用这个方法来获取缓存, 且标记为`非调试模式`
    const getcache = () => {
      console.log(`[ERROR] 调试模式: 正在使用缓存的页面!`)
      boxdata.syscfgs.isDebugMode = false
      return $.getjson($.KEY_web_cache).cache
    }
    await $.http.get($.web).then(
      (resp) => {
        if (/<title>BoxJs<\/title>/.test(resp.body)) {
          // 返回页面源码, 并马上存储到持久化仓库
          $.html = resp.body
          const cache = { version: $.version, cache: $.html }
          $.setjson(cache, $.KEY_web_cache)
        } else {
          // 如果返回的页面源码不是预期的, 则从持久化仓库中获取
          $.html = getcache()
        }
      },
      // 如果获取页面源码失败, 则从持久化仓库中获取
      () => ($.html = getcache())
    )
  }
  // 根据偏好设置, 替换首屏颜色 (如果是`auto`则交由页面自适应)
  const theme = $.getdata('@chavy_boxjs_userCfgs.theme')
  if (theme === 'light') {
    $.html = $.html.replace('#121212', '#fff')
  } else if (theme === 'dark') {
    $.html = $.html.replace('#fff', '#121212')
  }
  /**
   * 后端渲染数据, 感谢 https://t.me/eslint 提供帮助
   *
   * 如果直接渲染到 box: null 会出现双向绑定问题
   * 所以先渲染到 `boxServerData: null` 再由前端 `this.box = this.boxServerData` 实现双向绑定
   */
  $.html = $.html.replace('boxServerData: null', 'boxServerData:' + JSON.stringify(boxdata))

  // 调试模式支持 vue Devtools (只有在同时开启调试模式和指定了调试地址才生效)
  // vue.min.js 生效时, 会导致 @click="window.open()" 报 "window" is not defined 错误
  if (isDebugWeb && debugger_web) {
    $.html = $.html.replace('vue.min.js', 'vue.js')
  }
}

/**
 * 处理`查询`请求
 */
async function handleQuery() {
  const [, query] = $.path.split('/query')
  if (/^\/boxdata/.test(query)) {
    $.json = getBoxData()
  } else if (/^\/baks/.test(query)) {
    const globalbaks = getGlobalBaks(true)
    $.json = { globalbaks }
  } else if (/^\/versions$/.test(query)) {
    await getVersions(true)
  }
}

/**
 * 处理 API 请求
 */
async function handleApi() {
  const [, api] = $.path.split('/api')

  if (api === '/save') {
    await apiSave()
  } else if (api === '/addAppSub') {
    await apiAddAppSub()
  } else if (api === '/reloadAppSub') {
    await apiReloadAppSub()
  } else if (api === '/delGlobalBak') {
    await apiDelGlobalBak()
  } else if (api === '/updateGlobalBak') {
    await apiUpdateGlobalBak()
  } else if (api === '/saveGlobalBak') {
    await apiSaveGlobalBak()
  } else if (api === '/impGlobalBak') {
    await apiImpGlobalBak()
  } else if (api === '/revertGlobalBak') {
    await apiRevertGlobalBak()
  } else if (api === '/runScript') {
    await apiRunScript()
  }
}

async function handleOptions() {}

/**
 * ===================================
 * 获取基础数据
 * ===================================
 */

function getBoxData() {
  const datas = {}
  const usercfgs = getUserCfgs()
  const sessions = getAppSessions()
  const curSessions = getCurSessions()
  const sysapps = getSystemApps()
  const syscfgs = getSystemCfgs()
  const appSubCaches = getAppSubCaches()
  const globalbaks = getGlobalBaks()

  // 把 `内置应用`和`订阅应用` 里需要持久化属性放到`datas`
  sysapps.forEach((app) => Object.assign(datas, getAppDatas(app)))
  usercfgs.appsubs.forEach((sub) => {
    const subcache = appSubCaches[sub.url]
    if (subcache && subcache.apps && Array.isArray(subcache.apps)) {
      subcache.apps.forEach((app) => Object.assign(datas, getAppDatas(app)))
    }
  })

  const box = { datas, usercfgs, sessions, curSessions, sysapps, syscfgs, appSubCaches, globalbaks }
  return box
}

/**
 * 获取系统配置
 */
function getSystemCfgs() {
  // prettier-ignore
  return {
    env: $.isLoon() ? 'Loon' : $.isQuanX() ? 'QuanX' : $.isSurge() ? 'Surge' : 'Node',
    version: $.version,
    versionType: $.versionType,
    envs: [
      { id: 'Surge', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/surge.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/surge.png'] },
      { id: 'QuanX', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/quanX.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/quantumultx.png'] },
      { id: 'Loon', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/loon.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/loon.png'] }
    ],
    chavy: { id: 'ChavyLeung', icon: 'https://avatars3.githubusercontent.com/u/29748519', repo: 'https://github.com/chavyleung/scripts' },
    senku: { id: 'GideonSenku', icon: 'https://avatars1.githubusercontent.com/u/39037656', repo: 'https://github.com/GideonSenku' },
    id77: { id: 'id77', icon: 'https://avatars0.githubusercontent.com/u/9592236', repo: 'https://github.com/id77' },
    orz3: { id: 'Orz-3', icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/Orz-3.png', repo: 'https://github.com/Orz-3/' },
    boxjs: { id: 'BoxJs', show: false, icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/box.png', icons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/box.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/box.png'], repo: 'https://github.com/chavyleung/scripts' },
    defaultIcons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/appstore.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/appstore.png']
  }
}

/**
 * 获取内置应用
 */
function getSystemApps() {
  // prettier-ignore
  const sysapps = [
    {
      id: 'BoxSetting',
      name: '偏好设置',
      descs: ['可设置 http-api 地址 & 超时时间 (Surge TF)', '可设置明暗两种主题下的主色调'],
      keys: [
        '@chavy_boxjs_userCfgs.httpapi', 
        '@chavy_boxjs_userCfgs.bgimg', 
        '@chavy_boxjs_userCfgs.color_dark_primary', 
        '@chavy_boxjs_userCfgs.color_light_primary'
      ],
      settings: [
        { id: '@chavy_boxjs_userCfgs.httpapis', name: 'HTTP-API (Surge TF)', val: '', type: 'textarea', placeholder: ',examplekey@127.0.0.1:6166', autoGrow: true, rows: 2, persistentHint:true, desc: '示例: ,examplekey@127.0.0.1:6166! 注意: 以逗号开头, 逗号分隔多个地址, 可加回车' },
        { id: '@chavy_boxjs_userCfgs.httpapi_timeout', name: 'HTTP-API Timeout (Surge TF)', val: 20, type: 'number', persistentHint:true, desc: '如果脚本作者指定了超时时间, 会优先使用脚本指定的超时时间.' },
        { id: '@chavy_boxjs_userCfgs.bgimgs', name: '背景图片清单', val: '无,\n跟随系统,跟随系统\nlight,http://api.btstu.cn/sjbz/zsy.php\ndark,https://uploadbeta.com/api/pictures/random\n妹子,http://api.btstu.cn/sjbz/zsy.php', type: 'textarea', placeholder: '无,{回车} 跟随系统,跟随系统{回车} light,图片地址{回车} dark,图片地址{回车} 妹子,图片地址', persistentHint:true, autoGrow: true, rows: 2, desc: '逗号分隔名字和链接, 回车分隔多个地址' },
        { id: '@chavy_boxjs_userCfgs.bgimg', name: '背景图片', val: '', type: 'text', placeholder: 'http://api.btstu.cn/sjbz/zsy.php', persistentHint:true, desc: '输入背景图标的在线链接' },
        { id: '@chavy_boxjs_userCfgs.color_light_primary', name: '明亮色调', canvas: true, val: '#F7BB0E', type: 'colorpicker', desc: '' },
        { id: '@chavy_boxjs_userCfgs.color_dark_primary', name: '暗黑色调', canvas: true, val: '#2196F3', type: 'colorpicker', desc: '' }
      ],
      author: '@chavyleung',
      repo: 'https://github.com/chavyleung/scripts/blob/master/box/switcher/box.switcher.js',
      ico
Download .txt
gitextract_iq6s9hbq/

├── .gitignore
├── Readme.md
├── docs/
│   ├── 01-overview.md
│   ├── 02-Docker.md
│   ├── 03-rules.md
│   ├── 04-JS.md
│   ├── 05-rewrite.md
│   ├── 06-task.md
│   ├── 07-feed&notify.md
│   ├── 08-logger&efss.md
│   ├── 09-webhook.md
│   ├── 10-config.md
│   ├── Advanced.md
│   ├── Readme.md
│   ├── dev_note/
│   │   ├── archive/
│   │   │   ├── favend JS 重构-efh.md
│   │   │   ├── minishell subprocess.md
│   │   │   ├── webUI.md
│   │   │   └── websocket 通信协议设计.md
│   │   ├── clash delegate efh.md
│   │   ├── elecV2P 错误自检指南.md
│   │   ├── ev 命令行程序.md
│   │   ├── favend JS 重构-efh.md
│   │   ├── favend 模块化.md
│   │   ├── readme.md
│   │   ├── runJSFile 执行逻辑及优化.md
│   │   ├── script_store.efh 应用中心.md
│   │   ├── service workers 开发与优化.md
│   │   ├── sse 通信模块.md
│   │   ├── store 常量加密存储读取.md
│   │   ├── webUI transparent mode.md
│   │   ├── webUI 主题设计.md
│   │   ├── webUI 首页快捷运行程序 eapp.md
│   │   ├── webhook token 权限设计.md
│   │   ├── 下载其他扩展程序.md
│   │   ├── 关于引入区块链的可行性.md
│   │   ├── 可能永不执行的长期计划.md
│   │   ├── 启动器快捷方式 $run.md
│   │   ├── 开发者激励计划.md
│   │   ├── 引入广告系统.md
│   │   ├── 待深度优化部分.md
│   │   ├── 根据 mitmhost 生成 pac 文件.md
│   │   ├── 脚本缓存_内容结果等.md
│   │   ├── 节点互联.md
│   │   └── 通过脚本管理规则 $rewrite.md
│   └── res/
│       └── logo/
│           └── readme.md
├── examples/
│   ├── JS-elecV2P.sublime-build
│   ├── JSTEST/
│   │   ├── 0body.js
│   │   ├── TGbotonFavend.js
│   │   ├── aria2-env.js
│   │   ├── asyncPool.js
│   │   ├── boxjs.ev.js
│   │   ├── cheerio-hbin.js
│   │   ├── efh/
│   │   │   ├── kuwo-music.efh
│   │   │   ├── markdown.efh
│   │   │   ├── notepad.efh
│   │   │   └── readme.md
│   │   ├── evui-chatroom.js
│   │   ├── evui-dou.js
│   │   ├── exam-ahk-send.js
│   │   ├── exam-ahk.js
│   │   ├── exam-chcp.js
│   │   ├── exam-clipboard.js
│   │   ├── exam-rss.js
│   │   ├── exam-tasksub.js
│   │   ├── example-cheerio.js
│   │   ├── example-rule.js
│   │   ├── fendtest.efh
│   │   ├── github-subdownload.js
│   │   ├── markdown.efh
│   │   ├── reboot.js
│   │   ├── simple.efh
│   │   ├── starturl.js
│   │   ├── tgbotmessage.js
│   │   └── webonlinetest.js
│   ├── Readme.md
│   ├── Shell/
│   │   ├── aria2c
│   │   ├── elecV2P-runjs.ahk
│   │   ├── exam-request.py
│   │   ├── mousemove.ahk
│   │   └── sendkey.ahk
│   ├── TGbotonCFworker.js
│   ├── TGbotonCFworker2.0.js
│   ├── archive/
│   │   └── Caddyfile
│   ├── docker-compose-clash.yaml
│   ├── docker-compose.yaml
│   ├── ev2p-nginx.conf
│   └── theme/
│       ├── elecV2P_theme.20220420.json
│       └── readme.md
└── information/
    ├── readme.md
    ├── 广告位招租.md
    └── 开发者激励计划.md
Download .txt
SYMBOL INDEX (111 symbols across 11 files)

FILE: examples/JSTEST/TGbotonFavend.js
  constant CONFIG_EV2P (line 86) | let CONFIG_EV2P = {
  function fetch (line 126) | function fetch(url) {
  function Request (line 139) | function Request(url, init = {}) {
  function Response (line 147) | function Response(body, header) {
  function surlName (line 202) | function surlName(url) {
  function timeoutPromise (line 214) | function timeoutPromise({ timeout = CONFIG_EV2P.timeout || 5000, fn }) {
  function getLogs (line 221) | function getLogs(s){
  function delLogs (line 234) | function delLogs(logn) {
  function getStatus (line 244) | function getStatus() {
  function getInfo (line 254) | function getInfo(debug) {
  function getTaskinfo (line 258) | function getTaskinfo(tid) {
  function opTask (line 263) | function opTask(tid, op) {
  function saveTask (line 275) | function saveTask() {
  function taskNew (line 279) | function taskNew(taskinfo) {
  function jsRun (line 311) | function jsRun(fn) {
  function getJsLists (line 322) | function getJsLists() {
  function deleteJS (line 332) | function deleteJS(name) {
  function shellRun (line 336) | function shellRun(command) {
  function storeManage (line 345) | function storeManage(keyvt) {
  function storeList (line 389) | function storeList() {
  function getFile (line 399) | function getFile(file_id) {
  function handlePostRequest (line 413) | async function handlePostRequest(request) {
  function handleRequest (line 793) | async function handleRequest(request) {
  function readRequestBody (line 821) | async function readRequestBody(request) {
  function tgPush (line 850) | async function tgPush(payload) {

FILE: examples/JSTEST/aria2-env.js
  method cb (line 13) | cb(data, error, success){

FILE: examples/JSTEST/asyncPool.js
  function promisePool (line 16) | function promisePool(fn, params, { cb, limit = 6, log = false }) {

FILE: examples/JSTEST/boxjs.ev.js
  function getHost (line 118) | function getHost(url) {
  function getPath (line 126) | function getPath(url) {
  function handlePage (line 143) | async function handlePage() {
  function handleQuery (line 213) | async function handleQuery() {
  function handleApi (line 228) | async function handleApi() {
  function handleOptions (line 252) | async function handleOptions() {}
  function getBoxData (line 260) | function getBoxData() {
  function getSystemCfgs (line 286) | function getSystemCfgs() {
  function getSystemApps (line 309) | function getSystemApps() {
  function getUserCfgs (line 358) | function getUserCfgs() {
  function getAppSubCaches (line 374) | function getAppSubCaches() {
  function getGlobalBaks (line 384) | function getGlobalBaks(isComplete = false) {
  function getVersions (line 397) | function getVersions() {
  function getUserApps (line 413) | function getUserApps() {
  function getAppSessions (line 421) | function getAppSessions() {
  function getCurSessions (line 428) | function getCurSessions() {
  function getAppDatas (line 438) | function getAppDatas(app) {
  function apiSave (line 457) | async function apiSave() {
  function apiAddAppSub (line 467) | async function apiAddAppSub() {
  function apiReloadAppSub (line 478) | async function apiReloadAppSub() {
  function apiDelGlobalBak (line 488) | async function apiDelGlobalBak() {
  function apiUpdateGlobalBak (line 499) | async function apiUpdateGlobalBak() {
  function apiRevertGlobalBak (line 510) | async function apiRevertGlobalBak() {
  function apiSaveGlobalBak (line 536) | async function apiSaveGlobalBak() {
  function apiImpGlobalBak (line 554) | async function apiImpGlobalBak() {
  function apiRunScript (line 562) | async function apiRunScript() {
  function reloadAppSubCache (line 619) | function reloadAppSubCache(url) {
  function reloadAppSubCaches (line 636) | async function reloadAppSubCaches() {
  function upgradeUserData (line 650) | function upgradeUserData() {
  function doneBox (line 676) | function doneBox() {
  function getBaseDoneHeaders (line 686) | function getBaseDoneHeaders(mixHeaders = {}) {
  function getHtmlDoneHeaders (line 697) | function getHtmlDoneHeaders() {
  function getJsonDoneHeaders (line 702) | function getJsonDoneHeaders() {
  function doneOptions (line 708) | function doneOptions() {
  function donePage (line 717) | function donePage() {
  function doneQuery (line 726) | function doneQuery() {
  function doneApi (line 736) | function doneApi() {
  function GistBox (line 750) | function GistBox(e){const t=function(e,t={}){const{isQX:s,isLoon:n,isSur...
  function Env (line 756) | function Env(t,e){class s{constructor(t){this.env=t}send(t,e="GET"){t="s...

FILE: examples/JSTEST/evui-dou.js
  class Widget (line 6) | class Widget {
    method constructor (line 7) | constructor() {
    method getDay (line 163) | getDay(dayNumber) {
  function showChart (line 302) | function showChart(imgurl, userName, total, title) {

FILE: examples/JSTEST/exam-chcp.js
  method cb (line 4) | cb(data, error){

FILE: examples/JSTEST/exam-clipboard.js
  method cb (line 2) | cb(data, error){

FILE: examples/JSTEST/github-subdownload.js
  function getTree (line 22) | async function getTree(apigit) {
  function main (line 32) | async function main(tree, dest, options = { recursive: true, onlyreg: ''...
  function mulDownload (line 70) | function mulDownload(url, dest, name) {

FILE: examples/JSTEST/tgbotmessage.js
  constant CONFIG (line 13) | const CONFIG = {

FILE: examples/TGbotonCFworker.js
  constant CONFIG_EV2P (line 55) | const CONFIG_EV2P = {
  function getLogs (line 67) | function getLogs(s){
  function delLogs (line 77) | function delLogs(logn) {
  function getStatus (line 87) | function getStatus() {
  function getTaskinfo (line 97) | function getTaskinfo(tid) {
  function opTask (line 107) | function opTask(tid, op) {
  function saveTask (line 120) | function saveTask() {
  function jsRun (line 130) | function jsRun(fn) {
  function getJsLists (line 141) | function getJsLists() {
  function deleteJS (line 151) | function deleteJS(name) {
  function handlePostRequest (line 169) | async function handlePostRequest(request) {
  function handleRequest (line 277) | async function handleRequest(request) {
  function readRequestBody (line 296) | async function readRequestBody(request) {

FILE: examples/TGbotonCFworker2.0.js
  constant CONFIG_EV2P (line 87) | let CONFIG_EV2P = {
  function surlName (line 161) | function surlName(url) {
  function timeoutPromise (line 173) | function timeoutPromise({ timeout = CONFIG_EV2P.timeout || 5000, fn }) {
  function getLogs (line 180) | function getLogs(s){
  function delLogs (line 193) | function delLogs(logn) {
  function getStatus (line 203) | function getStatus() {
  function getInfo (line 213) | function getInfo(debug) {
  function getTaskinfo (line 223) | function getTaskinfo(tid) {
  function opTask (line 234) | function opTask(tid, op) {
  function saveTask (line 252) | function saveTask() {
  function taskNew (line 262) | function taskNew(taskinfo) {
  function jsRun (line 300) | function jsRun(fn) {
  function getJsLists (line 311) | function getJsLists() {
  function deleteJS (line 321) | function deleteJS(name) {
  function shellRun (line 331) | function shellRun(command) {
  function storeManage (line 346) | function storeManage(keyvt) {
  function storeList (line 396) | function storeList() {
  function getFile (line 406) | function getFile(file_id) {
  function handlePostRequest (line 420) | async function handlePostRequest(request) {
  function handleRequest (line 815) | async function handleRequest(request) {
  function readRequestBody (line 837) | async function readRequestBody(request) {
  function tgPush (line 863) | async function tgPush(payload) {
Condensed preview — 91 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (434K chars).
[
  {
    "path": ".gitignore",
    "chars": 4,
    "preview": ".git"
  },
  {
    "path": "Readme.md",
    "chars": 492,
    "preview": "## elecV2P 文档/例程/通知/反馈 - documents/examples/information/issues\r\n\r\n主项目地址: https://github.com/elecV2/elecV2P\r\n\r\n![](https:"
  },
  {
    "path": "docs/01-overview.md",
    "chars": 8258,
    "preview": "```\r\n最近更新: 2022-10-21\r\n适用版本: 3.7.3\r\n```\r\n\r\n*此章节内容同步自 [elecV2P Readme 文档](https://github.com/elecV2/elecV2P)*\r\n\r\n## 简介\r\n\r"
  },
  {
    "path": "docs/02-Docker.md",
    "chars": 4548,
    "preview": "```\r\n最近更新: 2022-03-15\r\n适用版本: 3.6.3\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/02-Docker.md\r\n```\r\n\r\n##"
  },
  {
    "path": "docs/03-rules.md",
    "chars": 3658,
    "preview": "```\r\n最近更新: 2022-03-24\r\n适用版本: 3.7.8\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/03-rules.md\r\n```\r\n\r\n## "
  },
  {
    "path": "docs/04-JS.md",
    "chars": 34209,
    "preview": "```\r\n最近更新: 2024-11-10\r\n适用版本: 3.8.1\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/04-JS.md\r\n```\r\n\r\n**每个脚本"
  },
  {
    "path": "docs/05-rewrite.md",
    "chars": 4604,
    "preview": "```\r\n最近更新: 2021-10-16\r\n适用版本: 3.5.0\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/05-rewrite.md\r\n```\r\n\r\n#"
  },
  {
    "path": "docs/06-task.md",
    "chars": 10108,
    "preview": "```\n最近更新: 2022-02-08\n适用版本: 3.6.0\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/06-task.md\n```\n\n![task](ht"
  },
  {
    "path": "docs/07-feed&notify.md",
    "chars": 5470,
    "preview": "```\r\n最近更新: 2022-08-04\r\n适用版本: 3.6.9\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/07-feed&notify.md\r\n```\r"
  },
  {
    "path": "docs/08-logger&efss.md",
    "chars": 9002,
    "preview": "```\r\n最近更新: 2022-10-03\r\n适用版本: 3.7.2\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/08-logger&efss.md\r\n```\r"
  },
  {
    "path": "docs/09-webhook.md",
    "chars": 11937,
    "preview": "```\r\n最近更新: 2022-10-30\r\n适用版本: 3.7.4\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/09-webhook.md\r\n```\r\n\r\nw"
  },
  {
    "path": "docs/10-config.md",
    "chars": 10629,
    "preview": "```\n最近更新: 2022-11-06\n适用版本: 3.7.5\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/10-config.md\n```\n\n## 配置文件说"
  },
  {
    "path": "docs/Advanced.md",
    "chars": 6120,
    "preview": "```\r\n最近更新: 2023-03-22\r\n适用版本: 3.7.8\r\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/Advanced.md\r\n```\r\n\r\n## "
  },
  {
    "path": "docs/Readme.md",
    "chars": 1356,
    "preview": "## [elecV2P](https://github.com/elecV2/elecV2P) 使用说明\n\n*可直接跳转到想了解的部分,不必按顺序查看*\n\n1. [overview - 简介及基础安装](https://github.com"
  },
  {
    "path": "docs/dev_note/archive/favend JS 重构-efh.md",
    "chars": 6358,
    "preview": "```\n近期更新: 2021-12-09 22:10\n```\n\n### 原因\n\nelecV2P favend 中的 JS 可能包含 html 部分,直接使用 JS 进行插入,不够优雅。主要是在写的时候,代码编译器默认是 JS 高亮模式,而 "
  },
  {
    "path": "docs/dev_note/archive/minishell subprocess.md",
    "chars": 387,
    "preview": "### minishell 子进程交互\n\ninit subprocess list\n\n前提:每条 command 起一个 id\n\nsend('shell', {\n\tid,\n\ttype: 'main|sub',\n\tdata: 'command"
  },
  {
    "path": "docs/dev_note/archive/webUI.md",
    "chars": 908,
    "preview": "# web 重构计划(基本完成)\n\n## 左侧导航栏菜单 menu\n\n- OVERVIEW\n  - Port web/proxy/查看请求(anyproxy)\n  - rules/rewrite/task/mitm list lenght\n"
  },
  {
    "path": "docs/dev_note/archive/websocket 通信协议设计.md",
    "chars": 618,
    "preview": "### websocket 通信协议设计\n\n基础: json-RPC\n参考: telegram api\n\n### 内部函数管理/初始化\n\n函数和数据分离\n\n服务器端\n- 处理客户端发送过来的数据,对应 method\n- 更新 method\n"
  },
  {
    "path": "docs/dev_note/clash delegate efh.md",
    "chars": 130,
    "preview": "### clash_delegate.efh\n\nclash webUI for elecV2P, work on favend.\n\n两个模块\n\n- delegate manage\n- delegate rule\n\n默认分流 delegate"
  },
  {
    "path": "docs/dev_note/elecV2P 错误自检指南.md",
    "chars": 524,
    "preview": "### 后端运行问题\n\n- 查看 errors.log\n- 查看 pm2-error.log\n- 查看对应脚本的日志文件\n- 查看后台运行日志(docker logs elecv2p)\n- webUI->SETTING->日志等级调整为 d"
  },
  {
    "path": "docs/dev_note/ev 命令行程序.md",
    "chars": 572,
    "preview": "### ev binary 可执行程序\n\nelecV2P 可执行程序\n\n形式,要实现的功能\n\n``` sh\nev -help, -h     # 帮助菜单\n\n# elecV2P 全局控制类\nev start     # 开始 elecV2P"
  },
  {
    "path": "docs/dev_note/favend JS 重构-efh.md",
    "chars": 6535,
    "preview": "```\n近期更新: 2021-12-09 22:10\n```\n\n### 原因\n\nelecV2P favend 中的 JS 可能包含 html 部分,直接使用 JS 进行插入,不够优雅。主要是在写的时候,代码编译器默认是 JS 高亮模式,而 "
  },
  {
    "path": "docs/dev_note/favend 模块化.md",
    "chars": 706,
    "preview": "*将 EFSS 的 favorite 和 backend 结合起来有没有搞头*\n\n### 原因\n\n虽然 efh 文件很好的解决了前后端配合的问题,但当大量引用外部文件时,可能比较分散。比如在当前目录下引入 js 文件,在 efss 中引入 "
  },
  {
    "path": "docs/dev_note/readme.md",
    "chars": 197,
    "preview": "### elecV2P 开发笔记\n\n有时候想到一个功能,得先捋清一下思路,才能开始码代码。有些功能可能比较复杂,所以用笔记的形式记录一下。\n\n当时的笔记可能不是最终实现的样子,仅供参考(其实也没有什么参考意义,如果感兴趣的话,可以稍微看看\n"
  },
  {
    "path": "docs/dev_note/runJSFile 执行逻辑及优化.md",
    "chars": 416,
    "preview": "### runJSFile 函数逻辑\n\n执行的脚本类型\n\n- 本地文件\n- 远程文件\n- rawcode\n\n命名\n\nrunJSFile(filename, addContext={})\n\n```\ntype       filename   "
  },
  {
    "path": "docs/dev_note/script_store.efh 应用中心.md",
    "chars": 1177,
    "preview": "### 基础格式\n\n参考苹果、安卓应用中心的分类模式。\n\n``` JSON\n{\n  \"category\": \"类别\",\n  \"scripts\": [\n    {\n      \"name\": \"name.js\",\n      \"logo\": "
  },
  {
    "path": "docs/dev_note/service workers 开发与优化.md",
    "chars": 1581,
    "preview": "```\n最近更新: 2022-09-03 09:20\n文档地址: https://github.com/elecV2/elecV2P-dei/blob/master/docs/dev_note/service%20workers%20开发与"
  },
  {
    "path": "docs/dev_note/sse 通信模块.md",
    "chars": 1708,
    "preview": "### server-sent events\n\n需求分析:\n\n- send message(or no)\n\n问题:\n\n- 多用户(同一个请求路径\n- 多连接(单用户多个请求路径\n- 多请求(单连接发送多次数据\n- 多函数(不同数据对应不同处"
  },
  {
    "path": "docs/dev_note/store 常量加密存储读取.md",
    "chars": 259,
    "preview": "### 目的\n\n常量只属于某个脚本,或者必须提供某个密码才能查看。\n\n$store.put('value 原始', 'secret_key', {\n  pass: 'owowogld',\n  algo: 'ebuf',\n})\n\n$store"
  },
  {
    "path": "docs/dev_note/webUI transparent mode.md",
    "chars": 783,
    "preview": "## 功能\n\n将 webUI 临时作为一个“透明”代理,转发请求到其他任意端口,甚至任意服务器。\n\n## 配置\n\n``` JSON\n\"transparent\": {\n  \"enable\": true,\n  \"host\": \"127.0.0."
  },
  {
    "path": "docs/dev_note/webUI 主题设计.md",
    "chars": 1668,
    "preview": "### 基本格式\n\n``` JSON\n{\n  \"themeone\": {\n    \"name\": \"主题名称\",\n    \"note\": \"一些说明\",\n    \"color\": {\n      \"--main-bk\": \"#003153\""
  },
  {
    "path": "docs/dev_note/webUI 首页快捷运行程序 eapp.md",
    "chars": 2441,
    "preview": "### 基础说明\n\n![EAPP](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/eapp_overview.png)\n\n一个类似于手机主界面的模块"
  },
  {
    "path": "docs/dev_note/webhook token 权限设计.md",
    "chars": 1767,
    "preview": "### 目的\n\n限制某个 token 可访问的目录/时间/次数等。\n\n### 实现\n\n- 可设置多个 token\n\n每个 token 对应的权限:(返回 info\n- 完整权限(管理员\n- 可访问路径。比如 限制某个 token 除 log"
  },
  {
    "path": "docs/dev_note/下载其他扩展程序.md",
    "chars": 175,
    "preview": "### 简单说明\n\n直接下载其他可执行程序。\n\n### 基础格式\n\n``` JSON\n[{\n  \"name\": \"程序名称\",\n  \"resource\": \"url/to/download\",\n  \"file_type\": \"zip | t"
  },
  {
    "path": "docs/dev_note/关于引入区块链的可行性.md",
    "chars": 3054,
    "preview": "```\n初始发布: 2021-12-11 14:53\n最近更新: 2022-09-20 11:53\n```\n\n### 目的\n\n- 防止脚本被更改\n- 脚本分布式存储\n- 给予脚本作者一定奖励\n\n### 基本架构\n\n暂命名为 **EVP**,"
  },
  {
    "path": "docs/dev_note/可能永不执行的长期计划.md",
    "chars": 82,
    "preview": "### 重命名\n\n原因:现在的名称 elecV2P 不好发音,考虑取一个方便读写的名字。\n\n备选:\n\n- moefi\n- moeku\n- kumoe\n- kufee"
  },
  {
    "path": "docs/dev_note/启动器快捷方式 $run.md",
    "chars": 2140,
    "preview": "## 目的\n\n用一个 .json 文件作为启动的快捷方式,方便将多个动作进行组合,及一键运行。\n\n## 运行\n\n应该包含的动作,或者说功能:\n\n- 关键字: 对应功能\n\n具体任务类\n- script: 运行脚本\n- shell: 执行 sh"
  },
  {
    "path": "docs/dev_note/开发者激励计划.md",
    "chars": 981,
    "preview": "## 开发者激励计划\n\n简单说明:给予 elecV2P 脚本开发者一定的现金奖励。\n\n奖励金额:激励计划总金额 10000 元,发完即止。\n\n活动截止时间:2022-06-30\n\n奖励说明:单个脚本奖励 10-100 元不等,本次活动每个作"
  },
  {
    "path": "docs/dev_note/引入广告系统.md",
    "chars": 1076,
    "preview": "### 目的\n\n盈利。\n让项目能更好的发展,以及支持**开发者激励计划**。\n让在 elecV2P 编写脚本的开发者可以受益。脚本开发者有动力写脚本,用户就有更多的脚本可用,实现一个正向循环。\n\n### 广告位\n\n在 webUI 首页底部增"
  },
  {
    "path": "docs/dev_note/待深度优化部分.md",
    "chars": 186,
    "preview": "### elecV2P 中还可以进行深度优化的地方\n\n- runJSFile context 抽离共用的部分\n- EFSS 文件列表 hash table or Map\n- runJSFile cache JS 内容\n- 脚本运行资源释放的"
  },
  {
    "path": "docs/dev_note/根据 mitmhost 生成 pac 文件.md",
    "chars": 576,
    "preview": "### 说明\n\nPAC 文件地址: webUI/pac 。 比如 http://127.0.0.1/pac 或者 https://xx.xxx(你的webUI地址)/pac\n\n*[PAC 是什么?](https://developer.mo"
  },
  {
    "path": "docs/dev_note/脚本缓存_内容结果等.md",
    "chars": 631,
    "preview": "### 缓存结构\n\n``` JS\nconst script_cache = new Map();\nscript_cache.set('script_name', {\n  name: \"name\",\n  hash: md5(code),\n  "
  },
  {
    "path": "docs/dev_note/节点互联.md",
    "chars": 100,
    "preview": "### 简介\n\n不同的 elecV2P 服务器通过 websocket 连接。\n\n### 形式\n\nminishell connect userid\n\n### 功能 - 连接后可以做什么\n\n- 脚本传输"
  },
  {
    "path": "docs/dev_note/通过脚本管理规则 $rewrite.md",
    "chars": 162,
    "preview": "### 前期准备\n\nclass rewrite {\n  list()\n  add()\n  remove()\n  update()\n  find()\n}\n\n### 基础使用\n\n- $rewrite.list/add/remove/update"
  },
  {
    "path": "docs/res/logo/readme.md",
    "chars": 741,
    "preview": "### elecV2P logo 文件夹\n\n地址: https://github.com/elecV2/elecV2P-dei/tree/master/docs/res/logo\n\n主色彩值: #003153\n\nfavicon-32x32."
  },
  {
    "path": "examples/JS-elecV2P.sublime-build",
    "chars": 628,
    "preview": "// sublimet text build system 文件\r\n// 功能:在 sublime 编辑器中使用 ctrl + B 快速运行测试脚本\r\n// 使用:\r\n//   - 复制本文件到 sublime xxxx\\Data\\Pack"
  },
  {
    "path": "examples/JSTEST/0body.js",
    "chars": 44,
    "preview": "$done({ response: { body: 'elecV2P body' }})"
  },
  {
    "path": "examples/JSTEST/TGbotonFavend.js",
    "chars": 29019,
    "preview": "/**\n * 功能: elecV2P TGbot on favend\n * 参考修改自: https://github.com/elecV2/elecV2P-dei/blob/master/examples/TGbotonCFworker2"
  },
  {
    "path": "examples/JSTEST/aria2-env.js",
    "chars": 685,
    "preview": "// 需提前配置好 aria2 使用环境, 使命令 aria2c 在系统 Shell 环境中可用\r\n\r\n// task runjs example:\r\n// 运行 JS: aria2-env.js -e dlink=https://raw."
  },
  {
    "path": "examples/JSTEST/asyncPool.js",
    "chars": 2540,
    "preview": "/**\n * 异步并行执行函数及限制(待优化)\n * 已实现功能:\n * - 可在 cb 函数中动态添加参数\n * - 可在 cb 中提前结束运行\n * 待优化部分:\n * - 逻辑再写得清晰/简洁一点?\n * author     htt"
  },
  {
    "path": "examples/JSTEST/boxjs.ev.js",
    "chars": 32148,
    "preview": "// elecV2P v3.2.3 版本后,可直接使用 chavyleung 的原版 boxjs。本脚本不再维护\n// \n// BoxJs elecV2P 兼容版。修改自:https://github.com/chavyleung/scri"
  },
  {
    "path": "examples/JSTEST/cheerio-hbin.js",
    "chars": 259,
    "preview": "let body = $response.body\nlet restype = $response.headers['Content-Type']\n\nif (/html/.test(restype)) {\n  const $ = $chee"
  },
  {
    "path": "examples/JSTEST/efh/kuwo-music.efh",
    "chars": 21825,
    "preview": "<!-- 适用于 elecV2P v3.7.2 及以上版本 -->\n<!-- 脚本仅供测试使用,请勿用于其他用途 -->\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n"
  },
  {
    "path": "examples/JSTEST/efh/markdown.efh",
    "chars": 6433,
    "preview": "<!-- efh 小应用,适用于 elecV2P\n功能:一个简单的 markdonw 文件阅读器\n使用:\n- 打开 webUI/efss -> favend 设置\n- 填写任意名称/关键字 | 运行脚本 | https://raw.gith"
  },
  {
    "path": "examples/JSTEST/efh/notepad.efh",
    "chars": 2102,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"theme-color\" content=\"#003153\">\n  <meta http-equiv="
  },
  {
    "path": "examples/JSTEST/efh/readme.md",
    "chars": 615,
    "preview": "### efh 文件适用于 elecV2P favend\n\n更多说明,参考说明文档 [08-logger&efss.md](https://github.com/elecV2/elecV2P-dei/blob/master/docs/08-"
  },
  {
    "path": "examples/JSTEST/evui-chatroom.js",
    "chars": 737,
    "preview": "// 两个设备分别运行此脚本,可实现一个简单的聊天室。\n// 关于 $evui 的更多说明参考 https://github.com/elecV2/elecV2P-dei/tree/master/docs/04-JS.md 相关部分\n\nle"
  },
  {
    "path": "examples/JSTEST/evui-dou.js",
    "chars": 9141,
    "preview": "// (!!测试脚本)该脚本用于在前端网页显示近几天的京豆变化。适用环境: elecV2P\n// 参考修改自:https://github.com/dompling/Scriptable/blob/master/Scripts/JDDouK"
  },
  {
    "path": "examples/JSTEST/exam-ahk-send.js",
    "chars": 88,
    "preview": "// 通过 JS 配合 autohotkey 发送文字和按键信息\n\n$exec('sendkey.ahk \"english or 中文 ^v ctrl+v is send\"')"
  },
  {
    "path": "examples/JSTEST/exam-ahk.js",
    "chars": 85,
    "preview": "// 一个配合 autohotkey 移动鼠标的小脚本\n\n$exec('mousemove.ahk left', {\n  cwd: './script/Shell'\n})"
  },
  {
    "path": "examples/JSTEST/exam-chcp.js",
    "chars": 131,
    "preview": "const command = 'CHCP 65001'\r\n\r\n$exec(command, {\r\n  cb(data, error){\r\n    error ? console.error(error) : console.log(dat"
  },
  {
    "path": "examples/JSTEST/exam-clipboard.js",
    "chars": 132,
    "preview": "$exec('powershell -command \"Get-Clipboard | echo\"', {\n  cb(data, error){\n    error ? console.error(error) : console.log("
  },
  {
    "path": "examples/JSTEST/exam-rss.js",
    "chars": 1273,
    "preview": "// 使用 cheerio 解析 rss 的小例子\n// iOS 限免软件推送。需要先设置好 IFTTT\n\nconst feedurl = 'https://rsshub.app/telegram/channel/BaccanoSoul/%"
  },
  {
    "path": "examples/JSTEST/exam-tasksub.js",
    "chars": 1123,
    "preview": "// 通过 webhook 添加定时任务订阅。运行前根据具体情况修改 suburl 和 webhook 里面的内容\n// 每次运行都会添加新任务,请不要多次运行\n// 这只是一个简单的范例,如果出现未知问题,手动修正一下代码\n\nconst "
  },
  {
    "path": "examples/JSTEST/example-cheerio.js",
    "chars": 408,
    "preview": "// a simply $cheerio eaxmple. modify from cheerio readme.md\n\nconst $ = $cheerio.load(`<ul id=\"fruits\">\n  <li class=\"appl"
  },
  {
    "path": "examples/JSTEST/example-rule.js",
    "chars": 354,
    "preview": "// a example for rule\r\n\r\n// $request.headers, $request.body, $request.method, $request.hostname, $request.port, $request"
  },
  {
    "path": "examples/JSTEST/fendtest.efh",
    "chars": 1580,
    "preview": "<title>efh fend 测试</title>\n<h3>efh - elecV2P favend html, 一个同时包含前后端运行代码的 html 扩展格式。</h3>\n<p>目前仅可运行于 elecV2P favend, 相关说明"
  },
  {
    "path": "examples/JSTEST/github-subdownload.js",
    "chars": 3047,
    "preview": "// 功能: github 子目录文件下载(仅适用于 elecV2P\n// 作者: https://t.me/elecV2\n// 文件地址: https://raw.githubusercontent.com/elecV2/elecV2P/"
  },
  {
    "path": "examples/JSTEST/markdown.efh",
    "chars": 5138,
    "preview": "<!-- efh 小应用,适用于 elecV2P\n功能:一个简单的 markdonw 文件阅读器\n使用:\n- 打开 webUI/efss -> favend 设置\n- 填写任意名称/关键字 | 运行脚本 | https://raw.gith"
  },
  {
    "path": "examples/JSTEST/reboot.js",
    "chars": 189,
    "preview": "// 系统重启脚本,谨慎使用,无法取消\n\nconst countdown = 30              // 重启等待时间,单位:秒\nconsole.log('操作系统将在', countdown, '后重启')\n\nsetTimeou"
  },
  {
    "path": "examples/JSTEST/simple.efh",
    "chars": 894,
    "preview": "<h3>一个简单的 efh 格式示例文件</h3>\n<div><label>请求后台数据测试</label><button onclick=\"dataFetch()\">获取</button></div>\n\n<script type=\"tex"
  },
  {
    "path": "examples/JSTEST/starturl.js",
    "chars": 94,
    "preview": "/**\r\n * windows 上通过默认浏览器打开对应 url.\r\n */\r\n\r\n$exec('start https://github.com/elecV2/elecV2P', {})"
  },
  {
    "path": "examples/JSTEST/tgbotmessage.js",
    "chars": 1365,
    "preview": "/**\r\n * 功能:使用TG bot 给频道或他人发信息\r\n * 使用方法:\r\n * 先使用 https://t.me/BotFather 创建一个机器人,获取 bot token\r\n * 然后获取目标用户的 chatid,填写到下面对应"
  },
  {
    "path": "examples/JSTEST/webonlinetest.js",
    "chars": 588,
    "preview": "// 网站是否在线监控\r\n\r\nurl = 'https://github.com/favicon.ico'  // 监控网址\r\n\r\nnew Promise(resolve => {\r\n  let note = ''\r\n  $axios(ur"
  },
  {
    "path": "examples/Readme.md",
    "chars": 2037,
    "preview": "## 主要用于一些测试和备份(仅供参考)\n\n### [JSTEST](https://github.com/elecV2/elecV2P-dei/tree/master/examples/JSTEST)\n\n一些测试 JS 文件,比如:\n\n-"
  },
  {
    "path": "examples/Shell/elecV2P-runjs.ahk",
    "chars": 1270,
    "preview": "; 功能:\n;   使用 win + j 快捷键快速执行选择代码或远程 JS\n; 使用: (提前安装好 autohotkey)\n;   1. 修改 webhook url 和 token 为 elecV2P 实际运行值\n;   2. 通过"
  },
  {
    "path": "examples/Shell/exam-request.py",
    "chars": 137,
    "preview": "import requests, json\r\n\r\nre = requests.get(\"http://httpbin.org/json\")\r\n# re.encoding = 'UTF-8'\r\n\r\njsre = json.loads(re.t"
  },
  {
    "path": "examples/Shell/mousemove.ahk",
    "chars": 611,
    "preview": "; 通过附带参数移动鼠标。 windows 平台使用,且已安装好 autohotkey\r\n; 示例:\r\n; mousemove.ahk left 400  ; 鼠标左移 400 Pixel\r\n; mousemove.ahk click 3"
  },
  {
    "path": "examples/Shell/sendkey.ahk",
    "chars": 395,
    "preview": "#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.\r\n; #Warn  ; Enable warnings to"
  },
  {
    "path": "examples/TGbotonCFworker.js",
    "chars": 10388,
    "preview": "/**\r\n * 说明:可部署到 cloudfalre worker 的 TGbot 后台代码,用于通过 telegram 查看/控制 elecV2P\r\n * 地址:https://github.com/elecV2/elecV2P-dei/"
  },
  {
    "path": "examples/TGbotonCFworker2.0.js",
    "chars": 29611,
    "preview": "/**\n * 功能: 部署在 cloudflare worker 的 TGbot 后台代码,用于通过 telegram 查看/控制 elecV2P\n * 地址: https://github.com/elecV2/elecV2P-dei/b"
  },
  {
    "path": "examples/archive/Caddyfile",
    "chars": 743,
    "preview": "{\r\nhttp_port   80\r\n}\r\n\r\nhttp://e2p.xxxxxxx.com {\r\n  log stdout\r\n  encode gzip\r\n  reverse_proxy 127.0.0.1:8100 {\r\n    hea"
  },
  {
    "path": "examples/docker-compose-clash.yaml",
    "chars": 864,
    "preview": "version: '3.7'\r\nservices:\r\n  elecv2p:\r\n    image: elecv2/elecv2p\r\n    container_name: elecv2p\r\n    restart: always\r\n    "
  },
  {
    "path": "examples/docker-compose.yaml",
    "chars": 662,
    "preview": "version: '3.7'\r\nservices:\r\n  elecv2p:\r\n    image: elecv2/elecv2p\r\n    container_name: elecv2p\r\n    restart: always\r\n    "
  },
  {
    "path": "examples/ev2p-nginx.conf",
    "chars": 1247,
    "preview": "server {\r\n  listen 80;\r\n  server_name e2p.xxxxxxx.com;\r\n  location / {\r\n    proxy_pass          http://127.0.0.1:8100;\r\n"
  },
  {
    "path": "examples/theme/elecV2P_theme.20220420.json",
    "chars": 1408,
    "preview": "[\n  {\n    \"name\": \"我的主题\",\n    \"mainbk\": \"#326733dd\",\n    \"appbk\": \"url(https://images.unsplash.com/photo-1646505183416-f"
  },
  {
    "path": "examples/theme/readme.md",
    "chars": 511,
    "preview": "### elecV2P 测试主题\n\n![elecV2P 测试主题](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/docs/res/theme_preview_01."
  },
  {
    "path": "information/readme.md",
    "chars": 202,
    "preview": "## elecV2P 活动详情\n\n用于发布一些 elecV2P 活动规则\n\n[广告位招租](https://github.com/elecV2/elecV2P-dei/blob/master/information/广告位招租.md)\n\n["
  },
  {
    "path": "information/广告位招租.md",
    "chars": 856,
    "preview": "### 预览图\n\n![elecV2P 广告位](https://raw.githubusercontent.com/elecV2/elecV2P-dei/master/information/res/sponsors_overview.pn"
  },
  {
    "path": "information/开发者激励计划.md",
    "chars": 1042,
    "preview": "## 简单说明\n\n每个兼容 elecV2P 的脚本,给予脚本作者 10-100 元不等的人民币奖励。首期总金额 10000 元,发完即止。\n\n截止时间:2022 年 6 月 30 号\n\n本次活动每个作者最高奖励 200 元,可以把机会留给更"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the elecV2/elecV2P-dei GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 91 files (316.1 KB), approximately 120.2k tokens, and a symbol index with 111 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!