为掘金编写了一款简易版一键三连的油猴脚本

1,900 阅读5分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

感觉创意大赛的奖品很丰富,想要多写几篇,发现是真的难~

这次的话,尝试着编写了一个非常简易的一键三连的油猴的脚本文件,内容对于前端开发者来说,应该算是入门级的吧~,对我来说,就没有那么那么容易~,大佬们,轻喷...

前言

我对于油猴插件一直是处于一种非常好奇的状态,感觉别人写出来的插件都特别特别厉害,最典型的就是读书的时候,写个插件,自动刷学习视频等等,网上还有去除广告、统计信息、导出文档、下载视频等等插件,很多很多。

从来没有接触过,就觉得它是一件学习成本非常高的事情,像是洪水猛兽一般,导致一直是搁置的状态,然后这次有活动,我就想到了这个点,就想要试一试。

接触下来后,其实写这样的脚本文件,远没有我想象的那么难,如果是对于前端来说,更没有啥学习成本,因为全部都是js代码~

一、实现效果

一键三连_

点击创建出来的一键三连的按钮,就可以实现点赞、关注、评论,下次看文不迷路~

实现逻辑倒是非常简单,但因为我对于前端没有那么熟悉,所以其中的一些坑,我都给躺了一遍~

二、完整代码

就是写着玩,完整代码如下:

 // ==UserScript==
 // @name          掘金牌一键三连
 // @namespace     https://juejin.cn/user/2859142558267559
 // @version       0.0.1
 // @description  打开博文,点击一键点赞评论按钮后自动为该博文点赞收藏关注,前提是已经登录 掘金
 // @author       宁在春
 // @include      *://juejin.cn/post/*
 // @include      *.juejin.cn/post/*
 // ==/UserScript==
 (function() {
     'use strict';
     var tag_list = document.createElement("div");
     tag_list.classList.add('tag-list'); // 给创建出来的div 添加一个 class
     var button = document.createElement("button"); //创建一个按钮
     button.textContent = "一键三连"; //按钮内容
     button.style.width = "90px"; //按钮宽度
     button.style.height = "32px"; //按钮高度
     button.style.align = "center"; //文本居中
     button.style.color = "white"; //按钮文字颜色
     button.style.background = "#e33e33"; //按钮底色
     button.style.border = "1px solid #e33e33"; //边框属性
     button.style.borderRadius = "4px"; //按钮四个角弧度
     button.style.fontSize="14px";
     tag_list.appendChild(button);
 ​
     /**
     * ele: html 元素
     * className (string): 要判断的类名
     * 返回值: 元素含有该类名返回 true,不包含返回 false
     */
     function hasClass (ele, className) {
         var reg = new RegExp('(^|\s)' + className + '(\s|$)')
         return reg.test(ele.className)
     }
 ​
     // 点赞
     function clickLike(){
         var likeElement=document.getElementsByClassName("panel-btn")[0];
         if(!hasClass(likeElement,"active")){
             likeElement.click(); //点赞
         }
     }
     // 关注
     function clickFollow(){
         var followElement=document.getElementsByClassName("follow-button")[0];
         if(!hasClass(followElement,"followed")){
             followElement.click(); //点击关注
         }
     }
     //获取焦点
     function getFocus() {
         document.getElementsByClassName('rich-input')[0].focus();
     };
     //打开emoje 表情选择框
     function getEmojeClick() {
         document.getElementsByClassName("emoji-box")[0].click();
     }
     var items = [112,102,92,80,71,66,61,55,31,28];
     var randomNumber=items[Math.floor(Math.random() * items.length)];
     //选择emoje表情
     function getChoseEmoje() {
         setTimeout(function(){
             var emojipicker = document.getElementsByClassName("emojipicker")[0];
             var emojeImage = emojipicker.getElementsByClassName("list")[1];
             var item = emojeImage.getElementsByClassName("item")[randomNumber];
             var img = item.getElementsByClassName("image")[0];
             img.click();
         },100);// setTimeout 0.1秒后执行
     }
 ​
     //发表评论
     function submitComment(){
         setTimeout(function(){
             document.getElementsByClassName("submit-btn")[0].click(); //发表评论
         },100);// setTimeout 0.1秒后执行
     }
 ​
     function clickBotton(){
         setTimeout(function(){
             //调用点赞方法
             clickLike()
             //调用关注方法
             clickFollow()
             //让评论框获取焦点
             getFocus()
             //打开emoje 表情选择框
             getEmojeClick()
             //选择emoje表情
             getChoseEmoje()
             //发表评论
             submitComment();
         },100);// setTimeout 0.1秒后执行
     }
 ​
     // 延时将元素添加到页面上,有时候页面加载缓慢,会导致,脚本执行完,但页面还加载完~,导致出现错误
     setTimeout(function(){
         var like_comment = document.getElementsByClassName('tag-list-box'); //getElementsByClassName 返回的是数组,所以要用[] 下标
         like_comment[0].appendChild(tag_list); //把按钮加入到 x 的子节点中
     },500);
 ​
     button.addEventListener("click", clickBotton) //监听按钮点击事件
 })(); //(function(){})() 表示该函数立即执行
 ​
 // ==UserScript==
 // @name          掘金牌一键三连
 // @namespace     https://juejin.cn/user/2859142558267559
 // @version       0.0.1
 // @description  打开博文,点击一键点赞评论按钮后自动为该博文点赞收藏关注,前提是已经登录 掘金
 // @author       宁在春
 // @include      *://juejin.cn/post/*
 // @include      *.juejin.cn/post/*

关于这些 @ 的标识,大家可以直接去搜一下油猴脚本的开发文档即可,当然这几个也很简单,就不多言了,如果感兴趣了,可以去找一找~

将这个脚本添加到油猴的脚本管理页面即可:

image.png (图片说明:点击添加新脚本)

image.png

(图片说明:把上面的代码,直接复制粘贴过来,Ctrl+S 保存就可以生效了)

三、踩坑

踩坑全部集中在添加评论那一步。

我原本的想法是

  • 构建好一个评论内容的数组
  • 每次一键三连时,从中随机抽取一条评论

然后踩坑之旅就开始啦~

我们先来看看掘金的评论框区域的代码:

image.png

 <div data-v-013fc4fa="" data-v-7c7c7498="" class="input-box">
     <div  contenteditable="true"  spellcheck="false"        
         placeholder="输入评论(Enter换行,Ctrl + Enter发送)"                 
         disabled="disabled" class="rich-input   empty">
      </div>
 </div>

这里的话,它是用用了一个 contenteditable来做为输入框的。

我总共是采取了三种方式去尝试实现~

3.1、直接改变 div 的内容

我的想法是,先触发div的点击方法,然后就会出现光标在输入框内,这样就可以改变其中内容啦。

第一次就这么开始啦~

             var comment=["针不戳呀,写的针不戳!","学习了!b( ̄▽ ̄)d","本文不错( ̄ˇ ̄),值得学习!(= ̄ω ̄=)","感谢博主的分享!(^ ^)/▽▽\(^ ^)","感谢博主,你的文章让我得到一些收获!( ̄ˇ ̄)"];
             var STARTNUMBER = -1;
             var ENDNUMBER = 5;
             var temp_count = Math.floor(Math.random()*(STARTNUMBER-ENDNUMBER+1))+ENDNUMBER 
            
             document.getElementsByClassName("input-box")[0].click(); //打开评论区
             document.getElementsByClassName('rich-input')[0].classList.remove("empty");
             document.getElementsByClassName("rich-input")[0].innerHTML = comment[temp_count]; //随机把一条预先写好的评论赋值到评论框里面
             document.getElementsByClassName("submit-btn")[0].click(); //发表评论

结果:

image.png

内容上去是上去了,但实际点击方法根本没有被触发,这只能说是改变了div的展示内容。

完全没有牵扯到值的变化,在这一步的时候,我又接着这个思路,将底下的按钮的禁用给去掉,手动提交了一遍,果然报评论为空的错误~


咋说勒,对这个想法不死心,我以为是我没有成功触发点击方法的问题,又接着去搜索怎么触发div的点击方法,搜了蛮多的,但是都不太对。

其中还有尝试了下面的这样一个方法:

 function emulateMouseClick (element) {
     // 创建事件
     var event = document.createEvent('MouseEvents')
     // 定义事件 参数: type, bubbles:事件冒泡, cancelable :是否可撤销
     event.initEvent('click', true, true)
     // 触发对象可以是任何元素或其他事件目标
     element.dispatchEvent(event)
 }

参考链接:

MDN Document.createEvent()

创建和触发事件(en-US)


这个时候,我又想到了 div 身上的 contenteditable="true" 属性,对这个属性有点陌生,感觉它应该对我有点帮助,然后就搜到了下面的内容:

image.png

(图片说明:链接

看到可编辑之后这几个字后,我又往这个方向继续去探索~

又继续搜到以下的内容:

如何让div获取焦点或可编辑

contenteditable联合v-html实现数据双向绑定的vue组件

让div获取焦点

Vue实现可编辑div获取焦点🎉

找着找着,我就去找如何让div触发焦点事件了,就又有了下面的尝试

3.2、尝试让div获取焦点

在这一步的时候,率先尝试的是下面的这个方法,因为这位作者尝试用Vue去实现掘金发送沸点的那个选择表情的功能,这篇文章也让我避免了一些坑~

Vue实现可编辑div获取焦点🎉 感谢作者~

这篇文章用了

image.png

以上三种方式来尝试实现,第一个 tabindex=0,好像是不行的,我就直接跳过了。

我又接着去找寻第二种方式:

发现下面这篇文章:

在可编辑div中定位光标和设置光标,还给了代码和实例

image.png

作者的源代码,大家可以点链接过去看一下,我就不增加篇幅了。

参考完上面作者的代码,我改成了下面这样:

 // 定义最后光标对象
 var lastEditRange;
 // 编辑框点击事件
 document.getElementsByClassName("rich-input")[0].onclick = function () {
     // 获取选定对象
     var selection = getSelection();
     // 设置最后光标对象
     lastEditRange = selection.getRangeAt(0);
 };
 // 编辑框按键弹起事件
 document.getElementsByClassName("rich-input")[0].onkeyup = function () {
     // 获取选定对象
     var selection = getSelection();
     // 设置最后光标对象
     lastEditRange = selection.getRangeAt(0);
 };
 ​
 // 发送表情
 function editContent() {
     // 获取可编辑框
     var editor = document.getElementsByClassName("rich-input")[0];
     // 获取输入框
     var comment = [
         "针不戳呀,写的针不戳!",
         "学习了!b( ̄▽ ̄)d",
         "本文不错( ̄ˇ ̄),值得学习!(= ̄ω ̄=)",
         "感谢博主的分享!(^ ^)/▽▽\(^ ^)",
         "感谢博主,你的文章让我得到一些收获!( ̄ˇ ̄)",
     ];
     var STARTNUMBER = -1;
     var ENDNUMBER = 5;
     var temp_count =
         Math.floor(Math.random() * (STARTNUMBER - ENDNUMBER + 1)) + ENDNUMBER; //取STARTNUMBER-ENDNUMBER之间的随机数 [STARTNUMBER,ENDNUMBER]
     var editComment = comment[temp_count];
 ​
     // 编辑框获得焦点
     editor.focus();
 ​
     // 获取选定对象
     var selection = getSelection();
     // 如果保存的有上次的光标对象
     if (lastEditRange) {
         // 清除所有选区
         selection.removeAllRanges();
         // 添加最后光标还原之前的状态
         selection.addRange(lastEditRange);
     }
     // 如果选定对象范围是编辑框范围
     // 创建表情文本节点进行插入
     var emojiText = document.createTextNode(editComment);
     // 如果文本框的子元素大于0,则表示有其他元素,则按照位置插入表情节点
     if (editor.childNodes.length > 0) {
         for (var i = 0; i < editor.childNodes.length; i++) {
             if (i == selection.anchorOffset) {
                 editor.insertBefore(emojiText, editor.childNodes[i]);
             }
         }
     } else {
         // 否则直接插入一个表情元素
         editor.appendChild(emojiText);
     }
     // 创建新的光标对象
     var range = document.createRange();
     // 将光标对象的范围界定为新建的表情节点
     range.selectNodeContents(emojiText);
     // 定位光标位置在表情节点的最大长度位置
     range.setStart(emojiText, emojiText.length);
 ​
     // 将选区折叠为一个光标
     range.collapse(true);
     // 清除所有光标对象A
     selection.removeAllRanges();
     // 添加新的光标对象
     selection.addRange(range);
     // 记录最后光标对象
     lastEditRange = selection.getRangeAt(0);
 }
 editContent();

执行效果如下:

image.png

(图片说明:focus 方法确实成功,点亮文本框了,但是内容的改变,仍然无法被监听)

包括光标的位置此时也改变了,但它仍然只算我修改了视图内容,因为评论的按钮仍然是暗的。

这个时候,我突然想到,我手动给它赋值不行,我可以操纵复制粘贴方法啊。

然后这个时候又开始踩坑了!!!

3.3、尝试使用copy、paste 方法

我只是隐约知道浏览器可以操作 copy、paste 方法,并没有尝试过。

又继续开始疯狂的搜索~

这次尝试之前,我先了试,确定在获取到div内容框的焦点后,cv是可以的之后,我就想在粘贴板上做点文章

我的思路是,先把评论内容制造好,然后用Js复制到粘贴版上,之后再调用 paste 方法将内容粘贴到 div 的那个内容框中去。

想法还是挺美好的。

参考着下面的内容进行着尝试

尝试了下面的内容

  1. Document.execCommand()方法
  2. 异步的 Clipboard API
  3. copy事件和paste事件

3.3.1、Document.execCommand()

最先尝试的 Document.execCommand() 事件,Chrome、Firefox 不支持

 document.execCommand('paste')

image.png

导致这一步直接GG 了~

3.3.2、Clipboard API

然后 Clipboard API 是偏向于操作剪贴板,就是可以写入,读出,但是并不是触发 paste方法,

我也找了一个小案例:

  <body>
     <p>hello world</p>
     <button id="btn-copy">copy text</button>
     <button id="btn-paste">copy text</button>
     <script>
       const copyBtn = document.querySelector('#btn-copy');
       
       copyBtn.addEventListener(
         'click',
         async function () {
           try {
             // 将 p 标签内的文本编辑后写入剪切板
             await navigator.clipboard.writeText(
               `${document.querySelector('p').textContent}!`
             );
           } catch (e) {
             console.error('Failed to copy: ', e);
           }
         },
         false
       );
     
       const pasteBtn = document.querySelector('#btn-paste');
 ​
       pasteBtn.addEventListener(
         'click',
         async function () {
           try {
             // 读取剪切板中的文本
             const text = await navigator.clipboard.readText();
             console.log(text);
           } catch (e) {
             console.error('Failed to read clipboard content: ', e);
           }
         },
         false
       );
     </script>
   </body>

这个坑也让我躺了,又去尝试下一个方法:

另外还参考了 MDN 文档:Element: paste event

3.3.3、clipboard.js

我也尝试了这个方法,但是我发现,它只能监听用户的 paste 事件,当时粘贴事件发生时,才能处理事件~

 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Document</title>
     <script
       type="text/javascript"
       src="https://cdn.bootcss.com/clipboard.js/2.0.1/clipboard.js"
     ></script>
   </head>
   <body>
     <!-- Target -->
     <input id="input" value="你好,我是宁在春" />
     <!-- Trigger -->
     <button class="copy">Copy to clipboard</button>
     <button id="paste">paste</button>
     <textarea id="output"> </textarea>
     <script>
       var clipboard = new ClipboardJS(".copy", {
         text: function (trigger) {
           return doCopy();
         },
       });
       var pastBtn = document.getElementById("output");
 ​
       pastBtn.addEventListener("paste", function (event) {
         console.log(pastBtn);
         if (event.clipboardData || event.originalEvent) {
           var clipboardData = event.clipboardData || window.clipboardData;
           var val = clipboardData.getData("text");
           if (val) {
             doPast(val);
           } else {
             throw new Error("剪切板内容为空或者非文本类型");
           }
           event.preventDefault();
         }
       });
       //获取要复制到剪切板的内容
       function doCopy() {
         return document.getElementById("input").value;
       }
 ​
       //处理剪切板内容
       function doPast(value) {
         alert(value);
       }
     </script>
   </body>
 </html>
 ​

补充:在这一步的时候,我原本还想继续去尝试用JavaScript 模拟键盘按键去尝试触发复制粘贴事件的,但是没有去尝试啦。

主要在网上搜到的知识,不敢说千篇一律,但是点开大都数文章略微有点让人失望~,还有一点就是转载来转载去(个人吐槽,轻喷),一篇文章能看见好几次,哈哈


后来想起第一篇看到的文章,聊到的掘金的表情,然后就有了下面的灵光一现~

3.4、灵光一现

这个时候,我想着,手动改你内容不行,直接点击表情,然后放到div中,这样总可以吧


因为只要打开emjoe 面板,点一下表情,就会自动的添加到那个内容框中去,这一次尝试竟然是ok的~

      // 获取评论区焦点
       function getFocus() {
         document.getElementsByClassName("rich-input")[0].focus();
       }
       //打开emoje 表情选择框
       function getEmojeClick() {
         document.getElementsByClassName("emoji-box")[0].click();
       }
       //选择emoje表情
       function getChoseEmoje() {
         var emojipicker = document.getElementsByClassName("emojipicker")[0];
         var emojeImage = emojipicker.getElementsByClassName("list")[1];
         var item = emojeImage.getElementsByClassName("item")[0];
         var spanImage = item.getElementsByClassName("byte-tooltip__wrapper")[0];
         var img = spanImage.querySelector("img");
         img.click();
       }
       getFocus();
       getEmojeClick();
       getChoseEmoje();

大家可以直接把这一步放到掘金的文章页面,F12后,将代码复制运行,是可行的~

在这一步可行之后,我又想在这个基础上增强,既然选择掘金的表情是可以的,那我先加一个表情,然后再加上我自己的评论内容,不就行了吗~,说做就做

代码不贴了,直接说结果,失败了~,还是和之前一模一样,发起请求的时候,根本没有携带这个数据内容

后记

最后实现效果大家也看到了,因为暂时没有找到更好的一个方法去实现,我最后用表情妥协了,表情选用了几个比较正向的表情,然后在评论的时候进行随机选择

代码比较偏向于基础,使用到的全部都是 JavaScript 中的基础知识,写出来也是我的一个尝试,大家喜欢与否,我并不能知晓。

关于这篇文章,如果是对于从来没有接触过油猴脚本的小伙伴来说,拿来看一看这么简单,也算是一个接触。

除了这一点外,我也不敢说此文,对读者而言,能有多少收获了。

如果感兴趣可以多做尝试,在这其中,多少会有一些收获。

对了,不要被陌生的事物吓住了,只要迈出第一步,那么就是成功的😀

我明白我对于前端领域,就是一位小白,文中不可避免可能会出现错误或遗漏,请大家一定要进行指出!

当然如果让你有所收获的话,也希望可以留下你的赞~,哈哈

大伙可以直接将代码放进油猴,来试一试这个脚本的效果,可以在这篇文章中,让我看到你们的实操吗~

也可以把第一个中 function 中的所有代码直接放到网页中执行,也是可以的~

写于 2022 年 8 月 7 日晚,作者:宁在春