上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

[油猴脚本开发指南]在控制台中调试油猴API函数

[复制链接]
  • TA的每日心情
    慵懒
    9 小时前
  • 签到天数: 813 天

    [LV.10]以坛为家III

    31

    主题

    552

    回帖

    1556

    积分

    荣誉开发者

    积分
    1556

    荣誉开发者新人进步奖油中2周年生态建设者新人报道挑战者 lv2油中3周年喜迎中秋

    发表于 2023-2-23 16:36:28 | 显示全部楼层 | 阅读模式

    本帖最后由 steven026 于 2023-2-23 16:37 编辑

    前言

    本文为新手向教程,只阐述如何在浏览器控制台(Devtools Console)中使用简便的方法调试油猴、脚本猫中以// @grant声明的API函数,不会深入探讨API函数如何使用,具体使用方法建议见各API函数指南(如果有😉)。
    众所周知,油猴以及脚本猫的API函数文档写的非常简单,很多函数需要传入的参数写的都模棱两可让人摸不着头脑,而且油猴没有直接能够调试的方法,静态代码每次都要刷新页面重新加载脚本,这就导致了油猴API函数调试非常麻烦。因此我们希望寻找一个简便的方法进行调试,合理且连贯的调试方法能在学习脚本开发时节约大量试错时间。
    另外本篇指南结论非常简单,但希望能阅读完全文理解其中原理以加深对油猴脚本的理解。

    理论知识

    推荐阅读以下文章/文档以更好理解本篇指南具体原理
    1.[油猴脚本开发指南]油猴的本质
    https://bbs.tampermonkey.net.cn/thread-2494-1-1.html
    2.[油猴脚本开发指南]grant介绍,none与unsafeWindow
    https://bbs.tampermonkey.net.cn/thread-160-1-1.html
    3.Scope(作用域)
    https://developer.mozilla.org/zh-CN/docs/Glossary/Scope


    首先油猴是一个浏览器插件,依靠插件API先将油猴框架及用户脚本(userscript)代码插入到脚本匹配的网页中,然后再执行脚本代码。
    从【油猴的本质】指南中可知,之所以油猴脚本能获取window全局上下文,是因为油猴脚本实际作用域是window全局作用域的子域。
    而常用的调试工具浏览器控制台(Devtools Console)访问的是top作用域,即window全局作用域,全局作用域无法直接访问子域。因此即使我们在油猴脚本中声明了@grant GM_xmlhttpRequest后正常运行脚本,我们直接在控制台中输入GM_xmlhttpRequest也无法对其进行调试,会提示GM_xmlhttpRequest is not defined,是因为全局作用域无法访问子作用的局部函数。
    虽然我们无法直接在全局作用域中访问子域,但是【JavaScript万物皆是对象】我们实际上需要访问的函数方法也是对象,只要知道其地址就可以访问到。因此我们可以将子域中的函数从子域中暴露到全局作用域中,使全局作用域也能直接访问到暴露的函数方法。
    这样,我们就可以对其进行调试。

    代码

    初级代码

    // ==UserScript==
    // @name         全局函数
    // @match        *://*/*
    // @grant        unsafeWindow
    // @grant        GM_info
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue
    // @grant        GM_getValue
    // @grant        GM_setClipboard
    // @grant        GM_download
    // @grant        GM_openInTab
    // @grant        GM_listValues
    // @grant        GM_cookie
    // ==/UserScript==
    
    unsafeWindow.GM_info=GM_info;
    unsafeWindow.GM_xmlhttpRequest=GM_xmlhttpRequest;
    unsafeWindow.GM_setValue=GM_setValue;
    unsafeWindow.GM_getValue=GM_getValue;
    unsafeWindow.GM_openInTab=GM_openInTab;
    unsafeWindow.GM_download=GM_download;
    unsafeWindow.GM_openInTab=GM_openInTab;
    unsafeWindow.GM_listValues=GM_listValues;
    unsafeWindow.GM_cookie_=GM_cookie;

    我们先以这10个API函数为例
    如果我们的油猴脚本需要用这10个API函数,就要现在脚本头部对其进行一一声明
    可以声明后不使用,而不可以不声明但使用(暂时不清楚声明过多会不会降低油猴运行效率)
    声明完函数后,我们才能够在脚本中正常使用(部分API函数不需要声明也可使用,这里不介绍)
    然后通过将该函数方法挂载到window对象unsafeWindow上以实现暴露到全局的目的。
    初级函数中重复unsafeWindow.XX=XX;即可达到目的

    进阶代码

    image.png
    我们拿GM_info试验一下,可以看到原本无法访问的函数对象现在可以正常在控制台中访问并进行调试了。
    观察GM_info返回的对象,可以看到我们之前声明的10个API函数也出现在了GM_info中,聪明的你看到这里是不是想到了什么?

    进阶代码1 eval

    // ==UserScript==
    // @name         全局函数
    // @match        *://*/*
    // @grant        unsafeWindow
    // @grant        GM_info
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue
    // @grant        GM_getValue
    // @grant        GM_setClipboard
    // @grant        GM_download
    // @grant        GM_openInTab
    // @grant        GM_listValues
    // @grant        GM_cookie
    // ==/UserScript==
    /* eslint no-eval:0 */
    
    for(const grant of GM_info.script.grant){
        unsafeWindow[grant]=eval(grant)
    }

    我们可以直接利用for循环+eval对其批量赋值进行暴露
    由于GM_info.script.grant中储存的只是API函数名称的字符串,并没有储存API函数本身,因此我们需要借助eval(或setTimeout)将字符串解析成函数,从而进行暴露。
    这样的好处是我们不需要再以静态的方法每声明一个API函数就静态暴露一次,而是可以通过for循环这种动态的方法批量暴露,即以后我们删改// @grant声明后,不需要再额外修改代码主体,将其全部交给for循环,重复利用代码,提高代码利用率,做到write less do more的效果。
    坏处也很明显,会触发eslint规则不要使用evaleslint: no-eval - eval can be harmful
    摆烂的话可以直接无视,或者加一行注释/* eslint no-eval:0 */来禁用这个eslint规则。
    如果不想摆烂又符合eslint规范,可以继续研究。

    进阶代码2 argument

    // ==UserScript==
    // @name         全局函数
    // @match        *://*/*
    // @grant        unsafeWindow
    // @grant        GM_info
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue
    // @grant        GM_getValue
    // @grant        GM_setClipboard
    // @grant        GM_download
    // @grant        GM_openInTab
    // @grant        GM_listValues
    // @grant        GM_cookie
    // ==/UserScript==
    
    console.log(arguments)
    for(let i=0;i<GM_info.script.grant.length;i++){
        unsafeWindow[GM_info.script.grant[i]]=arguments[i+3]
    }

    image.png
    先在脚本中加一行console.log(arguments),然后点击console的源头,可以看到油猴脚本的本质,argument中也一起传递了API函数的参数,这部分并不像GM_info中是字符串,而是真实的函数对象,直接将这部分对象暴露给全局即可,由于有index偏移,所以需要使用索引for循环
    注:油猴版本不同,传参的内容和顺序也可能不同,建议自己调试,顺便可以加深理解

    至此我们实现了3种调试在控制台中调试油猴API函数方法

    完结撒花

    (课后作业:通过//@require产生的变量是否也能用这类方式在控制台中调试? )

    实际应用

    (出处:https://bbs.tampermonkey.net.cn/thread-3917-1-1.html
    image.png

    已有1人评分好评 油猫币 理由
    朱焱伟 + 1 + 7 很给力!

    查看全部评分 总评分:好评 +1  油猫币 +7 

  • TA的每日心情
    开心
    前天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4196

    回帖

    4061

    积分

    管理员

    积分
    4061

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2023-2-23 17:26:02 | 显示全部楼层

    es规则可以去这里调试:https://eslint.org/play/

    另外脚本猫需要有 namespace之类的属性,否则保存会报错

    // ==UserScript==
    // @name         New Userscript
    // @namespace    https://bbs.tampermonkey.net.cn/
    // @version      0.1.0
    // @description  try to take over the world!
    // @author       You
    // @match        *://*/*
    // @grant        unsafeWindow
    // @grant        GM_info
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue
    // @grant        GM_getValue
    // @grant        GM_setClipboard
    // @grant        GM_download
    // @grant        GM_openInTab
    // @grant        GM_listValues
    // @grant        GM_cookie
    // ==/UserScript==
    
    for (const grant of GM_info.script.grant) {
        unsafeWindow[grant] = eval(grant)
    }
    
    const a = 1;
    
    a = 2;
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    9 小时前
  • 签到天数: 813 天

    [LV.10]以坛为家III

    31

    主题

    552

    回帖

    1556

    积分

    荣誉开发者

    积分
    1556

    荣誉开发者新人进步奖油中2周年生态建设者新人报道挑战者 lv2油中3周年喜迎中秋

    发表于 2023-2-24 08:51:45 | 显示全部楼层
    王一之 发表于 2023-2-23 17:26
    [md]es规则可以去这里调试:https://eslint.org/play/

    另外脚本猫需要有 `namespace`之类的属性,否则保存 ...

    脚本猫可以做个兼容么?不然有些脚本还要写个脚本猫专用和非脚本猫专用=-=
    还有比如脚本猫没有// @grant none可以视为非沙盒环境,而油猴没有的话则视为沙盒环境
    回复

    使用道具 举报

  • TA的每日心情
    开心
    前天 13:37
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4196

    回帖

    4061

    积分

    管理员

    积分
    4061

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2023-2-24 11:06:50 | 显示全部楼层
    steven026 发表于 2023-2-24 08:51
    脚本猫可以做个兼容么?不然有些脚本还要写个脚本猫专用和非脚本猫专用=-=
    还有比如脚本猫没有// @grant  ...

    啊?

    // @namespace    https://bbs.tampermonkey.net.cn/
    // @version      0.1.0
    // @description  try to take over the world!

    不本来就有的么
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

    发表回复

    本版积分规则

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