Greasy Fork

Greasy Fork is available in English.

腾讯验证码自动滑动

解决腾讯QQ登录验证码拖动问题

当前为 2022-04-14 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         腾讯验证码自动滑动
// @namespace    mscststs
// @version      0.2
// @description  解决腾讯QQ登录验证码拖动问题
// @author       mscststs
// @match        *://t.captcha.qq.com/cap_union_new_show*
// @icon         https://www.google.com/s2/favicons?domain=qq.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    // 工具库
    var mscststs= new class{
    sleep(miliseconds){
      return new Promise(resolve=>{
        setTimeout(()=>{resolve();},miliseconds);
      });
    }
    async _Step(selector,callback,need_content,timeout){
      while(timeout--){
                if(document.querySelector(selector)===null){
            await this.sleep(100);
          continue;
                }else{
                    if(need_content){
                            if(document.querySelector(selector).innerText.length==0){
                              await this.sleep(100);
                continue;
                        }
                    }
                }
        break;
      }

      callback(selector);
    }
    wait(selector,need_content = false,timeout=Infinity){
      return new Promise(resolve=>{
        this._Step(selector,function(selector){resolve(document.querySelector(selector));},need_content,timeout);
      });
    }
  }();
    async function eventHijacker(cb){
        let hijackEventList = ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"];
        function handler(e){
            if(!e.fake){
                // 拦截真事件,防止触发
                e.stopPropagation();
                e.preventDefault();
                return false
            }
        }
        hijackEventList.forEach(eventKey=>{
            window.addEventListener(eventKey,handler,true)
        })
        await cb()
        hijackEventList.forEach(eventKey=>{
            window.removeEventListener(eventKey,handler,true)
        })
    };
    (async function load(){
        // 初始化 ,监听canvas
        await mscststs.sleep(500)
        // console.log("重放!")
        await mscststs.wait("#slideBg")
        await mscststs.wait("#tcaptcha_drag_thumb")
        // await mscststs.sleep(500)
        // 重放开始
        eventHijacker(async ()=>{
            await replay(getTargetRecorder(fetchCanvas()-30,500),"#tcaptcha_drag_thumb")
            setTimeout(()=>{
                if(document.querySelector("#guideText").innerText.indexOf("对齐缺口")>0){
                    // 出现未对齐,重试一次
                    let event = new Event("click",{"bubbles":true, "cancelable":false});
                    event.fake = true;
                    document.querySelector(".tcaptcha-embed-refresh").dispatchEvent(event);
                    load()
                }
            },500)
        })

    })();

    // 亲自录制的老奶奶轨迹,成功率 100%
    const recoder = [{"type":"pointerdown","clientX":256,"clientY":709,"ts":2176},{"type":"pointermove","clientX":256,"clientY":709,"ts":2190},{"type":"pointermove","clientX":256,"clientY":707,"ts":2239},{"type":"pointermove","clientX":257,"clientY":707,"ts":2247},{"type":"pointermove","clientX":258,"clientY":707,"ts":2254},{"type":"pointermove","clientX":259,"clientY":707,"ts":2263},{"type":"pointermove","clientX":259,"clientY":708,"ts":2279},{"type":"pointermove","clientX":260,"clientY":708,"ts":2287},{"type":"pointermove","clientX":261,"clientY":708,"ts":2296},{"type":"pointermove","clientX":263,"clientY":708,"ts":2303},{"type":"pointermove","clientX":265,"clientY":708,"ts":2312},{"type":"pointermove","clientX":268,"clientY":708,"ts":2319},{"type":"pointermove","clientX":271,"clientY":709,"ts":2328},{"type":"pointermove","clientX":277,"clientY":709,"ts":2335},{"type":"pointermove","clientX":280,"clientY":710,"ts":2346},{"type":"pointermove","clientX":284,"clientY":710,"ts":2351},{"type":"pointermove","clientX":289,"clientY":711,"ts":2362},{"type":"pointermove","clientX":294,"clientY":711,"ts":2367},{"type":"pointermove","clientX":299,"clientY":713,"ts":2379},{"type":"pointermove","clientX":303,"clientY":713,"ts":2386},{"type":"pointermove","clientX":306,"clientY":713,"ts":2396},{"type":"pointermove","clientX":309,"clientY":713,"ts":2399},{"type":"pointermove","clientX":311,"clientY":713,"ts":2407},{"type":"pointermove","clientX":315,"clientY":713,"ts":2415},{"type":"pointermove","clientX":318,"clientY":713,"ts":2422},{"type":"pointermove","clientX":322,"clientY":713,"ts":2431},{"type":"pointermove","clientX":326,"clientY":713,"ts":2439},{"type":"pointermove","clientX":330,"clientY":713,"ts":2447},{"type":"pointermove","clientX":333,"clientY":713,"ts":2455},{"type":"pointermove","clientX":336,"clientY":713,"ts":2463},{"type":"pointermove","clientX":338,"clientY":714,"ts":2471},{"type":"pointermove","clientX":339,"clientY":714,"ts":2481},{"type":"pointermove","clientX":338,"clientY":714,"ts":2719},{"type":"pointermove","clientX":337,"clientY":716,"ts":2732},{"type":"pointermove","clientX":336,"clientY":716,"ts":2735},{"type":"pointermove","clientX":334,"clientY":716,"ts":2746},{"type":"pointermove","clientX":333,"clientY":716,"ts":2751},{"type":"pointermove","clientX":332,"clientY":716,"ts":2762},{"type":"pointermove","clientX":330,"clientY":716,"ts":2767},{"type":"pointermove","clientX":329,"clientY":716,"ts":2779},{"type":"pointermove","clientX":328,"clientY":716,"ts":2783},{"type":"pointermove","clientX":327,"clientY":716,"ts":2799},{"type":"pointermove","clientX":328,"clientY":716,"ts":3063},{"type":"pointermove","clientX":329,"clientY":716,"ts":3087},{"type":"pointermove","clientX":330,"clientY":716,"ts":3103},{"type":"pointermove","clientX":331,"clientY":716,"ts":3127},{"type":"pointermove","clientX":332,"clientY":716,"ts":3146},{"type":"pointermove","clientX":331,"clientY":716,"ts":3583},{"type":"pointermove","clientX":330,"clientY":716,"ts":3639},{"type":"pointermove","clientX":329,"clientY":716,"ts":3655},{"type":"pointerup","clientX":329,"clientY":716,"ts":4407},{"type":"click","clientX":329,"clientY":716,"ts":4420},{"type":"pointermove","clientX":329,"clientY":716,"ts":4422},{"type":"mousemove","clientX":329,"clientY":716,"ts":4422}]
    /**
    * ImageData 转色值矩阵
    */
    function imageDataToBrightnessArray(imageData,width,height){
        let brightnessArray = []
        let pixel_color = imageData
        let pointer = 0
        for(let i=0;i< height;i++)
        {
            brightnessArray[i] = []; //将每一个子元素又定义为数组
            for( let n=0;n< width;n++)
            {
                brightnessArray[i][n]= parseInt((pixel_color[pointer] + pixel_color[pointer+1] + pixel_color[pointer+2]) / 3) ; //此时pix[i][n]可以看作是一个二级数组
                pointer = pointer+4;
            }
        }
        return brightnessArray

    }
    function zipArray_row(b_array){
        let result = []
        let height = b_array.length;
        let width = b_array[0].length;
        for(let i = 0;i<width;i++){
            let val = 0;
            for(let j= 0;j<height ; j++){
                val += b_array[j][i]
            }
            result[i] = parseInt(val/height)
        }
        return result
    }
    function drawVline(ctx,left){
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(left, 0, 1, ctx.height)
    }
    function createCanvas(element){
        const {width,height} = element.getBoundingClientRect();
        let canvas = document.createElement("Canvas")
        canvas.width=width
        canvas.height=height
        document.body.appendChild(canvas)
        let ctx = canvas.getContext("2d")
        ctx.drawImage(element,0,0,width,height)
        return canvas
    }
    function getctx(selector){
        let bgCanvas = document.querySelector(selector);
        console.dir(bgCanvas)
        if(bgCanvas.tagName != 'CANVAS'){
            bgCanvas = createCanvas(bgCanvas)
        }
        let {width, height} = bgCanvas; // 宽度,高度
        let ctx = bgCanvas.getContext("2d");
        ctx.height = height;
        ctx.width = width;
        return ctx
    }
    function getImageData(selector){
        let ctx = getctx(selector)
        return ctx.getImageData(0,0,ctx.width,ctx.height).data
    }
    window.fetchCanvas = function(brightStep=10){
        //let fullBgData = getImageData("#slideBg") // 完整背景图
        let bgctx = getctx("#slideBg")
        let bgData = bgctx.getImageData(0,0,bgctx.width,bgctx.height).data // 残缺背景图
        window.brightnessArray = imageDataToBrightnessArray(bgData,bgctx.width,bgctx.height)

        window.brightnessArray_row = zipArray_row(window.brightnessArray)
        console.log(window.brightnessArray_row)
        let leftOffset = 0;
        window.brightnessChange_row = window.brightnessArray_row.map((item,index)=>{
            let next = window.brightnessArray_row[index+1] || item
            return {
                index:index,
                value:Math.abs(next-item)
            }
        })
        window.brightnessChange_row = window.brightnessChange_row.filter(item=>{
            if(item.index < bgctx.width*0.6){
                return false
            }
            return true
        })

        window.brightnessChange_row.sort((a,b)=>{
            return b.value-a.value
        })


        drawVline(bgctx ,window.brightnessChange_row[0].index)
        drawVline(bgctx ,window.brightnessChange_row[1].index)
        drawVline(bgctx ,window.brightnessChange_row[2].index)

        leftOffset = Math.min(window.brightnessChange_row[0].index, window.brightnessChange_row[1].index,window.brightnessChange_row[2].index)

        // console.log(window.brightnessChange_row)
        //throw new Error("暂停")
        // 拿到 leftOffset ,就是对应的缺口的偏移量
        console.log(leftOffset)
        return leftOffset
    }
    // 录制鼠标事件
    window.record = async function(){
        return await new Promise((resolve,reject)=>{
            let ms = new Date().valueOf();
            const getTime = ()=>{ // 时间打点
                return new Date().valueOf() - ms;
            }
            let eventList = [];
            function eventRecorder(e){
                let { type, clientX, clientY, target } = e;
                let eventMsg = { type, clientX, clientY, ts:getTime()}
                eventList.push(eventMsg)
            }
            // 开始录制
            window.addEventListener("keyup",(e)=>{
                ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"].forEach(eventKey=>{
                    window.addEventListener(eventKey,eventRecorder,true)
                })
                // 停止录制
                window.addEventListener("keyup",(e)=>{
                    ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"].forEach(eventKey=>{
                        window.removeEventListener(eventKey,eventRecorder,true)
                    })
                    resolve(eventList)
                },{once:true})
            },{once:true})
        })
    }
    // 轨迹压缩和重整
    window.getTargetRecorder = function getTargetRecorder(targetlength=100, targetduration=1000, recorder=recoder, ){
        console.log(`重放,长度 ${targetlength}, 时间 ${targetduration}`)
        // step 1. 首先以第一个事件的位置为起始,压缩整个轨迹
        let base = recorder[0];
        let ziped_recorder = recorder.map(event=>{
            return {
                type:event.type,
                offsetX:event.clientX - base.clientX,
                offsetY:event.clientY - base.clientY,
                ts:event.ts - base.ts
            }
        })
        // 压缩后的事件记录
        // console.log("ziped", ziped_recorder)
        let max = ziped_recorder[ziped_recorder.length -1] // 拿到最后一个
        ziped_recorder.reduce((p,e,i)=>{
            e.offsetX = parseInt(e.offsetX * targetlength / max.offsetX) // 轨迹缩放
            e.ts = parseInt(e.ts * targetduration / max.ts ) // 时间戳缩放
            return e
        })
        return ziped_recorder
    }
    // 轨迹事件重放
    window.replay = function(recorder, dom){
        if(typeof dom === "string"){
            dom = document.querySelector(dom)
        }
        let {left, top} = dom.getBoundingClientRect();
        left = left + 20* Math.random()
        top = top + 20* Math.random()
        return Promise.all(
            recorder.map(e=>{
                return new Promise(resolve=>{

                    setTimeout(()=>{
                        let type = "";
                        // 由于以前极验用的是pointer,现在要用mouse
                        if(e.type === "pointerdown") type = "mousedown";
                        if(e.type === "pointermove") type = "mousemove";
                        if(e.type === "pointerup") type = "mouseup";

                        let event = new Event(type,{"bubbles":true, "cancelable":false});

                        event.offsetX = e.offsetX;
                        event.offsetY = e.offsetY;
                        event.screenX = event.pageX = event.clientX = e.offsetX + left;
                        event.screenY = event.pageY = event.clientY = e.offsetY + top;
                        event.fake = true;
                        dom.dispatchEvent(event);
                        resolve()
                        //console.log("....",e.type,event)
                    },e.ts)
                })
            })
        )
    }
})();