腾讯验证码自动滑动

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

// ==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)
                })
            })
        )
    }
})();