steven026 发表于 2023-5-17 14:38:34

[油猴脚本开发指南]实现前端全自动唤醒本地后端

本帖最后由 steven026 于 2023-5-17 21:17 编辑

> 本帖最后由 steven026 于 2023-5-17 15:03 编辑

## 前言
本编指南内容较少、原理简单,更偏向于实现方案,主要作为理论实战篇打下基础(实战篇以后**有空**的时候会写)

写这篇指南原因主要是前端限制太大,无法满足所有日常(办公)自动化需求,部分需求(比如读写文件)需要通过后端来实现,但后端在部分方面(比如网页处理)灵活性不如前端,因此可以前后端相结合,各取其长,分工合作提升自动化效率。

本编指南
难易度:低(新手也可看懂)
实用性:中(省去手动运行本地后端时间)
广泛性:低(需要有本地后端程序且需要额外注册注册表)
(仅在windows中测试成功,其余环境未测试)
## 原理
windows支持通过注册表`regedit.exe`注册类似`https://`的本地自定义协议,可自定义设置`command`内容以实现通过自定义协议调用本地程序的方案。
而这个自定义协议亦可通过浏览器前端调用,限制较少,
因此前端使用自定义协议可以**单向**全自动唤醒本地后端并传输数据。
(双向需要通过XHR等方式,本文暂不讨论,可以等待日后的实战篇)
根据这一原理,使得油猴亦可调用本地后端,实现前后端相结合。

# 初级内容
## 准备工作
   
本地自定义协议实际上使用非常广泛,非常多的软件都在使用,因此我们可以直接根据原理*逆向*其他软件的实现方案。

***

!(data/attachment/forum/202305/17/131155zls77s7660ylsowu.png)   
!(data/attachment/forum/202305/17/131512ajcckz8clq6z6rq6.png)

**首先要在浏览器中允许页面打开新窗口,否则会被浏览器阻止**

以GitHub为例,在安装了GitHub Desktop后,可以通过一键打开`x-github-client://`协议,使浏览器将目标GiuHub仓库链接传递给本地的应用程序,减少用户操作,提升用户使用体验。

从控制台可以看到这是通过`<a>`标签打开的`x-github-client://`协议,
这就说明我们亦可通过`window.open(url)`打开这一协议链接。

***
!(data/attachment/forum/202305/17/131933imdn989b0b0kub2z.png)
在控制台尝试一下,
果然成功了,这就说明我们也可以在油猴脚本中使用这一特性。

***
那么问题就来了,这个协议怎么注册呢?查阅相关资料可以发现,这个自定义协议是通过注册表注册的。
因此打开`regedit.exe`注册表,搜索`x-github-client`
!(data/attachment/forum/202305/17/132439mvqauu4667vpr71c.png)
可以找到是这条注册表控制的协议注册
!(data/attachment/forum/202305/17/132617obds2w9t22wbdkto.png)
直接右键导出,可以获取如下数据,内容非常简单
```
Windows Registry Editor Version 5.00


"URL Protocol"=""
@="URL:x-github-client"






@="\"C:\\Users\\XXX\\AppData\\Local\\GitHubDesktop\\app-3.2.3\\GitHubDesktop.exe\" \"--protocol-launcher\" \"%1\""

```
可以看到这个注册表路径就是自定义协议名称`x-github-client`,而关键命令是最后一行的`command`,这个命令和cmd命令非常相似,
参考这个注册表就能注册自己的协议了。

## 注册表 test.reg
```
Windows Registry Editor Version 5.00


"URL Protocol"=""
@="URL:test"






@="\"C:\\Program Files\\nodejs\\node.exe\" \"D:\\test\\test.js\" \"%1\""

```
我们依样画葫芦,注册一个`test://`协议,利用nodejs调用`test.js`
将`x-github-client`全部替换为`test`
将最后一行`command`替换为我们自己需要调用的后端调用命令
记得将`"`和`\`转义
- `\"C:\\Program Files\\nodejs\\node.exe\"`是自定义程序绝对路径
- `\"D:\\test\\test.js\"`是自定义参数
- `\"%1\"`是打开的协议链接,这个最后不要改动,如果不需要传递数据的话可以删除

保存后双击`.reg`文件进行注册表注册。


至此我们完成了自定义协议注册工作,自定义协议无法通过前端完成,只能通过后端或者手动运行`.reg`文件进行注册。

***
!(data/attachment/forum/202305/17/133903gh6l7krsvs9hbrko.png)
我们在控制台中测试一下立马跳出了确认框,直接点击打开就行。
(注:仅在`https://`页面中,打开协议链接会弹出始终允许选项,如果勾选了始终允许后,后续在**该站点**打开**该协议**就不会再弹出二次确认了,会直接自动打开**该协议**
而在`http://`页面中没有始终允许的选项,每次打开协议都会弹出确认框**阻塞页面**,必须每次手动确认。这是浏览器特性,克服方法几乎没有,强行克服可以参考下文进阶内容)
!(data/attachment/forum/202305/17/133740opgoklugapgkgag4.png)
可以看到我们的`test.js`被成功打开了,而这个协议链接也成功被之前注册表`command`中的`%1`传递了过去,我们可以利用这个特性将前端数据自动传递给后端。
油猴脚本调用这个协议和控制台方法一样,只要在合适的时机运行原生函数即可`window.open("test://XXXXX")`

# 进阶内容
## 监听窗口事件
```js
testWindow = window.open('test://a')
if (testWindow) {
    console.log("窗口打开成功")
    testWindow.onunload = () => console.log("窗口被关闭")
} else {
    console.log("窗口打开失败,可能被浏览器阻止")
}
```
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/open
具体API使用方法不再赘述,如有需要直接查阅MDN文档即可。

由于自定义协议不需要用到特殊API,因此可以不使用脚本管理器的`GM_openInTab`API,直接用原生的`window.open`即可。
由于没有违反同源策略,因此可以监听新窗口的`onunload`事件。
(似乎无法监听用户是点击了确认还是取消,只能通过其他方法实现)
(如果勾选了`始终允许`后`onunload`事件发生即代表后端成功被调用)

## 在http页面中始终允许打开协议链接
!(data/attachment/forum/202305/17/141112ot4li34tap9w1t3u.png)
目前发现只有在`https://`或者`chrome-extension://`使用`window.open`打开自定义协议链接才会有`始终允许`的选项框.

***

!(data/attachment/forum/202305/17/141042gd8elrvyd8dvyd8c.png)
除此以外,连插件API`chrome.tabs.create`都不会弹出`始终允许`的选项框
(`GM_openInTab`也不会弹出因为其实现原理也是用了`chrome.tabs.create`)
据查阅文档说是出于安全考虑,禁用了`http`页面的`始终允许`选项。
但这也使得每次在`http`页面打开协议链接都会阻塞页面必须手动确认才行,非常影响自动化效率,实在不能忍!

***

因此经过漫长时间的研究,发现我们只要在脚本管理器的background页面运行`window.open`即可绕过浏览器的`http`页面限制。
那么怎么在脚本管理器的background页面运行代码呢?
改源码即可【……
由于油猴闭源,而脚本猫开源,因此我们直接爆改`GM_openInTab`API,然后把新代码去GitHub提交给脚本猫不就行了吗🙄。
详细源码可以查阅GitHub
https://github.com/scriptscat/scriptcat/pull/178
使用了`window.open`与`chrome.tabs`相结合的方式,
实现在`http`页面运行~~油猴~~脚本猫脚本亦可始终允许自动打开自定义协议链接
补上了最后一块拼图。
***
**该方法仅适用于脚本猫,这是一个实验性/不兼容其他管理器/不兼容Firefox的功能**
(火狐我用的实在太少,尝试了一下似乎background页面强制阻止使用`window.open`,无法通过这一原理实现,也有可能是我方法不对,如果有实现的大佬欢迎讨论或者提交PR)

使用方法非常简单
调用`GM_openInTab`函数,在参数中加上`{ useOpen: true }`即可
该方法做了向下兼容性处理,只有在新版(v0.12.0后的版本)脚本猫中使用了`{ useOpen: true }`才会生效使用新方法去打开链接,并会忽略其余所有参数(因为该方法不适用默认参数)
如果在油猴或者旧版脚本猫中即使加了该参数亦不会生效,不会造成其余不利的影响,只是会和旧`GM_openInTab`一样弹出没有`始终允许`的选项框而已
```js
const protocol = GM_openInTab('test://', { useOpen: true })
protocol.onclose = () => console.log("onclose")
```
## 完结撒花
具体脚本前后端相结合的自动化实战篇待有空时会写,【最好不要抱太大期望

~~实在写不动了……原本以为一个小时最多了,结果写了快2个小时,直接过载,灵思枯竭了,感觉还有点东西想写没写出来,一下子竟然想不起是啥了……等想起来再更新吧~~

脚本体验师001 发表于 2023-5-17 22:04:29

高,实在是高,简直有八九十层楼那么高

ThisAV 发表于 2023-5-19 17:09:39

就是走自定义协议,要么就是走CHrome内核的通讯接口,不管哪个都要写注册表

李恒道 发表于 2024-4-1 01:22:13

这个感觉逆向不是特别有必要吧

最近回顾了一下

可能直接使用protocol-registry npm库就可以实现协议自动注册了

李恒道 发表于 2024-4-1 03:17:50

哈人...protocol-registry半残了

steven026 发表于 2024-4-1 14:30:09

李恒道 发表于 2024-4-1 01:22
这个感觉逆向不是特别有必要吧

最近回顾了一下


能自己实现绝不require{:4_86:}
node一堆module看着就烦{:4_105:}
页: [1]
查看完整版本: [油猴脚本开发指南]实现前端全自动唤醒本地后端