欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nodejs实现抖音自动关注小姐姐小哥哥神器

程序员文章站 2022-04-30 08:09:10
...

抖音自动关注小姐姐小哥哥神器

创作来源:https://github.com/wangshub/Douyin-Bot 源作者用python实现,这里用node实现一个版本,修改了一些东西

此项目git仓库

  • [x] 自动翻页
  • [x] 颜值检测
  • [x] 人脸识别
  • [x] 自动点赞
  • [x] 自动关注

原理

  • 打开《抖音短视频》APP,进入主界面
  • 获取手机截图,并对截图进行压缩 (Size < 1MB);
  • 请求 人脸识别 API
  • 解析返回的人脸 Json 信息,对人脸检测切割;
  • 当颜值大于门限值 BEAUTY_THRESHOLD时,点赞并关注;
  • 下一页,返回第一步;

使用教程

  • 相关软件工具安装和使用步骤请参考 wechat_jump_gameAndroid 操作步骤
  • 上述环境事python,这里是使用的node,所以需要在本地装一下nodejs
  • ai.qq.com 免费申请 AppKeyAppID
    1. 获取源码:git clone [email protected]:sunlandong/douyin_bot_node.git
    2. 进入源码目录: cd douyin_bot_node
    3. 安装依赖: npm install
    4. 运行程序:node index.js

源码




var images = require("images");
var rp = require("request-promise");
var md5 = require("crypto-js/md5");
const url = 'https://api.ai.qq.com/fcgi-bin/face/face_detectface'
class DouyinGril {
    constructor() {
        const _ts = this,
            { execFile } = require('child_process');

        _ts.m = {
            path: require('path'),
            fs: require('fs'),
            os: require('os'),
            chalk: require('chalk'),
            execFile: execFile
        };

        _ts.config = {};
        _ts.screenData = {};
        _ts.config.os = (() => {
            let osName = _ts.m.os.type();
            return osName === 'Darwin' ? 'mac' : osName === 'Linux' ? 'linux' : 'win';
        })();
    }

    async init() {
        const _ts = this
        try {
            const v = await _ts.screenSize()
            let status = v.status,
                data = v.data || {};

            if (status === 'success') {
                _ts.screenData.width = data.width;
                _ts.screenData.height = data.height;
            }
        } catch (e) {
            _ts.log('error', e.status);
            _ts.log('error', e.msg);
            _ts.log('error', '请检查手机是否已经成功连接电脑并已经开启USB调试模式');
        }
    }

    /**
     * 截取手机屏幕并保存到项目的目录中
     * @param {string} imgName 图片名称
     */
    screencap(imgName) {
        const _ts = this;
        return new Promise((resolve, reject) => {
            _ts.adb(`shell screencap -p /sdcard/${imgName}`).then(v => {
                let savePath = _ts.m.path.join(__dirname, 'static', 'screen', imgName);
                _ts.adb(`pull /sdcard/${imgName} ${savePath}`).then(v => {
                    resolve({
                        status: 'success',
                        msg: '完成屏幕截图更新',
                        data: v
                    });
                }).catch(e => {
                    resolve({
                        status: 'success',
                        msg: '屏幕截图更新失败',
                        data: e
                    });
                });
            }).catch(e => {
                reject({
                    status: 'error',
                    msg: '截图失败',
                    data: e
                });
            });
        });
    }

    /**
     * 调用api参数签名
     * @param {参数} param 
     * @param {*} app_key 
     */
    getSignature(param, app_key) {
        let str = Object.keys(param).sort().map(key => {
            return `${key}=${encodeURIComponent(param[key])}`
        })
            .join("&") + "&app_key=" + app_key
        return md5(str).toString().toLocaleUpperCase()
    }

    /**
     * 改变图片尺寸
     * @param {图片名称} imgName 
     */
    resize_image(imgName) {
        const _ts = this;
        let savePath = _ts.m.path.join(__dirname, 'static', 'screen', imgName);
        return images(savePath).resize(1024).save(savePath, 'jpg', {
            quality: 90
        }).encode("jpg", {
            quality: 90                    //保存图片到文件,图片质量为50
        })
    }

    /**
     * 将buffer数据转成base64
     * @param {buffer} data 
     */
    imageBufferToBase64(data) {
        console.log(data)
        if (Buffer.isBuffer(data)) {
            return data.toString('base64')
        }
    }

    /**
     * 请求数据
     */
    async getFace(image) {
        let time = Number.parseInt(new Date().getTime() / 1000 + "")
        let app_id = "1106868413"
        let mode = 0
        let time_stamp = time
        let nonce_str = time + ""
        let params = {
            app_id: app_id,
            mode: mode,
            time_stamp: time_stamp,
            nonce_str: nonce_str,
            image: image,
            sign: this.getSignature({ app_id, mode, time_stamp, nonce_str, image }, "Iisf0QhGmxD0xksG")
        }
        const options = {
            method: 'POST',
            uri: url,
            form: params
        };
        // let redirect = 'https://jdm-test.0606.com.cn/strategy/135d630c6465f5ee3dc5b364'
        let res = await rp(options)
        return res
    }


    /**
     * 开始检测
     */
    async start() {
        const _ts = this;
        //第一步,先截图
        await this.screencap("faceOriginal.png")
        //处理图片
        const bufferdata = this.resize_image("faceOriginal.png")
        const base64 = this.imageBufferToBase64(bufferdata)
        //请求数据
        let res = await this.getFace(base64)
        res = JSON.parse(res)
        if (res.ret == 0) {
            _ts.log('success', JSON.stringify(res))
            let mybeauty = 0
            const { face_list } = res.data
            //一张图里包含多个人物
            for (let index = 0; index < face_list.length; index++) {
                let element = face_list[index];
                let { x, y, width, height, face_id, beauty, gender } = element
                images(images(_ts.m.path.join(__dirname, 'static', 'screen', "faceOriginal.png")), x, y, width, height).save(_ts.m.path.join(__dirname, 'static', 'face', face_id + ".png"))
                if (beauty > 0 && gender < 50) {
                    //gender越接近0,越是女性,
                    mybeauty = beauty
                    if (mybeauty > 80) {
                        break
                    }
                }

            }

            if (mybeauty > 80) {
                console.log("美女哈哈哈哈")
                await _ts.follow_user(500)
                await _ts.thumbs_up(500)
                // const screen = await this.screenSize()
                // this.log('tip','屏幕的宽高'+JSON.stringify(screen))
            }
        }
    }

    /**
     * 循环检测
     */
    async loop() {
        const _ts = this;
        while (true) {
            await _ts.next_page()
            await _ts.start()


        }
    }

    /**
   * 获取屏幕分辩率
   */
    screenSize() {
        const _ts = this;
        return new Promise((resolve, reject) => {
            _ts.adb('shell wm size').then(v => {
                if (typeof v === 'object' && typeof v.data === 'object' && typeof v.data.stdout === 'string') {
                    let data = v.data.stdout,
                        val = data.match(/\d{1,9}/ig);
                    resolve({
                        status: 'success',
                        msg: '屏幕分辩率获取成功',
                        data: {
                            width: val[0],
                            height: val[1]
                        }
                    })
                } else {
                    reject({
                        status: 'error',
                        msg: '屏幕分辩率获取失败',
                        data: {}
                    });
                };
            }).catch(e => {
                reject({
                    status: 'error',
                    msg: '屏幕分辩率获取出错',
                    data: e
                });
            });
        });
    }

    /**
     * adb命令
     * @param   {string} command adb命令字符串
     * @returns {object} 返回一个Promise对象
     */
    adb(command) {
        const _ts = this,
            m = _ts.m,
            config = _ts.config;

        return new Promise((resolve, reject) => {
            let adbFile = (() => {
                let extensionName = config.os === 'win' ? '.exe' : '';
                return m.path.join(__dirname, 'tool', config.os, 'adb' + extensionName);
            })();
            m.execFile(adbFile, command.split(' '), null, (error, stdout, stderr) => {
                if (error) {
                    reject({
                        status: 'error',
                        msg: `<adb ${command}> 执行错误`,
                        data: error
                    });
                } else {
                    resolve({
                        status: 'success',
                        msg: `<adb ${command}> 执行成功`,
                        data: {
                            stdout: stdout,
                            stderr: stderr
                        }
                    });
                };
            });
        });
    }

    /**
     * 打印日志
     * @param {日志类型} type 
     * @param {*} text 
     */
    log(type, text) {
        const _ts = this,
            m = _ts.m,
            log = console.log;

        switch (type) {
            case 'success':
                log(m.chalk.green(text));
                break;
            case 'error':
                log(m.chalk.red(text));
                break;
            case 'tip':
                log(m.chalk.yellow(text));
                break;
            default:
                log(text);
                break;
        };
    }

    /**
     * 暂停时间
     * @param {*} timeout 
     */
    stopTime(timeout) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, timeout);
        })
    }

    /**
     * 翻页
     */
    next_page() {
        const _ts = this
        return new Promise((resolve, reject) => {
            let
                x1 = _ts.screenData.width / 2,
                y1 = _ts.screenData.height / 2 + 305,
                x2 = _ts.screenData.width / 2,
                y2 = _ts.screenData.height / 2+5;

            _ts.adb(`shell input swipe ${x1} ${y1} ${x2} ${y2} 200`).then(async (v) => {
                await _ts.stopTime(1500)
                console.log(v)
                resolve({
                    status: 'success',
                    msg: '翻页',
                    data: v
                });
            }).catch(async (e) => {
                await _ts.stopTime(1500)
                reject({
                    status: 'error',
                    msg: '翻页失败',
                    data: e
                });
            });
        })
    }

    /**
     * 关注用户
     * @param {} time 
     */
    follow_user(time) {
        const _ts = this;
        return new Promise((resolve, reject) => {
            let x1 = _ts.screenData.width * 0.91 ,
            y1 = _ts.screenData.height / 2 * 0.98;


            _ts.adb(`shell input tap ${x1} ${y1}`).then(async(v) => {
                console.log(v)
                await _ts.stopTime(time)
                resolve({
                    status: 'success',
                    msg: '关注用户',
                    data: v
                });
            }).catch(e => {
                reject({
                    status: 'error',
                    msg: '关注用户失败',
                    data: e
                });
            });
        })
    }

    /**
     * 点赞
     * @param {} time 
     */
    thumbs_up(time) {
        const _ts = this;
        return new Promise((resolve, reject) => {
            let x1 = _ts.screenData.width * 0.91 ,
            y1 = _ts.screenData.height / 2 * 1.1;

            _ts.adb(`shell input tap ${x1} ${y1}`).then(async(v) => {
                console.log(v)
                await _ts.stopTime(time)
                resolve({
                    status: 'success',
                    msg: '点赞',
                    data: v
                });
            }).catch(e => {
                reject({
                    status: 'error',
                    msg: '点赞失败',
                    data: e
                });
            });
        })
    }
}


start = async ()=>{
    const douyin = new DouyinGril()
    await douyin.init()
    douyin.loop()
}


start()

依赖仓库

  1. “chalk”: “^2.4.1”
  2. “crypto-js”: “^3.1.9-1”
  3. “images”: “^3.0.1”
  4. “request”: “^2.87.0”
  5. “request-promise”: “^4.2.2”