boss直聘自动打招呼

boss直聘自动打招呼油猴脚本

// ==UserScript==
// @name         boss直聘自动打招呼
// @namespace    https://github.com/18023785187/auto-job
// @version      0.1.0
// @description  boss直聘自动打招呼油猴脚本
// @author       hym20000418
// @match        *://www.zhipin.com/*
// @icon         none
// @grant        none
// @license      MIT
// ==/UserScript==

/*
    修改该配置即可限定打招呼对象
*/
const config = {
    /**
     * 设置职位入口,可选值 0, 1, 2
     * 0 为以 搜索框搜索 作为职位入口
     * 1 为以 推荐职位——精选职位 作为职位入口
     * 2 为以 推荐职位——最新职位 作为职位入口
     * 
     * 设置三个入口是原因三个入口的职位都不太一样,避免漏了一些职位可以尝试切换入口
     */
    mode: 2,
    /**
     * 目标城市,
     * mode 为 1 或 2 时需要事先设置求职意向为目标城市,否则不生效
     */
    city: '深圳',
    /**
     * 职位关键词
     * mode 为 0 时作为搜索框的关键词键入
     * mode 为 1 或 2 时作为职位列表项中的职位名称匹配(这是因为推荐的职位不是很准确,比如偶尔会出现 “安卓工程师” 之类的职位,这时就需要通过关键词去过滤不匹配的职位)
     */
    keyword: '前端',
    /**
     * 职位名称要排除的关键字,比如 '高级前端工程师' 将会比排除在外
     */
    excludeKeywords: ['高级', '资深', '驻场', '外派', '安卓'],
    /**
     * 是否接受外地职位(职位列表有时会出现外地职位)
     */
    otherPlace: false,
    /**
     * 活跃度,匹配中的才会打招呼
     */
    liveness: ['在线', '刚刚活跃', '今日活跃', '3日内活跃'],
    /**
     * 要排除的公司名
     */
    excludes: ['中软国际', '德科', '睿服'],
    /**
     * 每次访问的最小间隔,防止操作过快被系统判定为机器人,单位秒
     */
    min: 3,
    /**
     * 每次访问的最大间隔,防止操作过快被系统判定为机器人,单位秒
     */
    max: 6,
    /**
     * 打招呼语
     */
    message: '您好,我正在找前端开发的工作,希望有机会与贵司进一步交流',
    /**
     * 工作年限,可多选
     * 经验不限     101
     * 应届生       102
     * 1年以内      103
     * 1-3年        104
     * 3-5年        105
     * 5-10年        106
     * 10年以上       107
     * 在校生          108
     */
    experience: [101, 103, 104, 105],
    /**
     * 薪资待遇,数字类型
     * 3K 以下      402
     * 3-5K         403
     * 5-10K        404
     * 10-20K       405
     * 20-50K       406
     * 50K以上      407
     * 
     * 自定义   [min, max] min表示最小值,max表示最大值,单位 K,如 salary: [13, 15] 表示 13-15K
     */
    salary: [],
    /**
     * 公司规模
     * 0-20人       301
     * 20-99人      302
     * 100-499人    303
     * 500-999人    304
     * 1000-9999人  305
     * 10000人以上  306
     */
    scale: [],
    /**
     * 学历要求
     * 大专 202
     * 本科 203
     * 硕士 204
     * 博士 205
     * 高中 206
     * 中专 208
     * 初中 209
     */
    degree: [],
}

var AutoJob = (function () {
    'use strict';

    /**
     * 监听元素是否生成
     */
    async function monitorElementGeneration(selector) {
        return new Promise(resolve => {
            let el;
            let timer = setInterval(() => {
                el = document.querySelector(selector);
                if(el) {
                    resolve(el);
                    clearInterval(timer);
                }
            }, 100);
        })
    }

    async function monitorElementsGeneration(selector) {
        return new Promise(resolve => {
            let el;
            let timer = setInterval(() => {
                el = document.querySelectorAll(selector);
                if(el.length) {
                    resolve(el);
                    clearInterval(timer);
                }
            }, 100);
        })
    }

    /**
     * 输入最大和最小正整数,在该范围内取随机数
     * @param {*} min 
     * @param {*} max 
     * @returns 
     */
    function random(min, max) {
        return (
            min + (Math.random() * (max - min))
        )
    }

    const cityCodeMap = {
        "北京": 101010100,
        "上海": 101020100,
        "天津": 101030100,
        "重庆": 101040100,
        "哈尔滨": 101050100,
        "齐齐哈尔": 101050200,
        "牡丹江": 101050300,
        "佳木斯": 101050400,
        "绥化": 101050500,
        "黑河": 101050600,
        "伊春": 101050700,
        "大庆": 101050800,
        "七台河": 101050900,
        "鸡西": 101051000,
        "鹤岗": 101051100,
        "双鸭山": 101051200,
        "大兴安岭地区": 101051300,
        "长春": 101060100,
        "吉林": 101060200,
        "四平": 101060300,
        "通化": 101060400,
        "白城": 101060500,
        "辽源": 101060600,
        "松原": 101060700,
        "白山": 101060800,
        "延边朝鲜族自治州": 101060900,
        "沈阳": 101070100,
        "大连": 101070200,
        "鞍山": 101070300,
        "抚顺": 101070400,
        "本溪": 101070500,
        "丹东": 101070600,
        "锦州": 101070700,
        "营口": 101070800,
        "阜新": 101070900,
        "辽阳": 101071000,
        "铁岭": 101071100,
        "朝阳": 101071200,
        "盘锦": 101071300,
        "葫芦岛": 101071400,
        "呼和浩特": 101080100,
        "包头": 101080200,
        "乌海": 101080300,
        "通辽": 101080400,
        "赤峰": 101080500,
        "鄂尔多斯": 101080600,
        "呼伦贝尔": 101080700,
        "巴彦淖尔": 101080800,
        "乌兰察布": 101080900,
        "锡林郭勒盟": 101081000,
        "兴安盟": 101081100,
        "阿拉善盟": 101081200,
        "石家庄": 101090100,
        "保定": 101090200,
        "张家口": 101090300,
        "承德": 101090400,
        "唐山": 101090500,
        "廊坊": 101090600,
        "沧州": 101090700,
        "衡水": 101090800,
        "邢台": 101090900,
        "邯郸": 101091000,
        "秦皇岛": 101091100,
        "太原": 101100100,
        "大同": 101100200,
        "阳泉": 101100300,
        "晋中": 101100400,
        "长治": 101100500,
        "晋城": 101100600,
        "临汾": 101100700,
        "运城": 101100800,
        "朔州": 101100900,
        "忻州": 101101000,
        "吕梁": 101101100,
        "西安": 101110100,
        "咸阳": 101110200,
        "延安": 101110300,
        "榆林": 101110400,
        "渭南": 101110500,
        "商洛": 101110600,
        "安康": 101110700,
        "汉中": 101110800,
        "宝鸡": 101110900,
        "铜川": 101111000,
        "济南": 101120100,
        "青岛": 101120200,
        "淄博": 101120300,
        "德州": 101120400,
        "烟台": 101120500,
        "潍坊": 101120600,
        "济宁": 101120700,
        "泰安": 101120800,
        "临沂": 101120900,
        "菏泽": 101121000,
        "滨州": 101121100,
        "东营": 101121200,
        "威海": 101121300,
        "枣庄": 101121400,
        "日照": 101121500,
        "聊城": 101121700,
        "乌鲁木齐": 101130100,
        "克拉玛依": 101130200,
        "昌吉回族自治州": 101130300,
        "巴音郭楞蒙古自治州": 101130400,
        "博尔塔拉蒙古自治州": 101130500,
        "伊犁哈萨克自治州": 101130600,
        "吐鲁番": 101130800,
        "哈密": 101130900,
        "阿克苏地区": 101131000,
        "克孜勒苏柯尔克孜自治州": 101131100,
        "喀什地区": 101131200,
        "和田地区": 101131300,
        "塔城地区": 101131400,
        "阿勒泰地区": 101131500,
        "石河子": 101131600,
        "阿拉尔": 101131700,
        "图木舒克": 101131800,
        "五家渠": 101131900,
        "铁门关": 101132000,
        "北屯市": 101132100,
        "可克达拉市": 101132200,
        "昆玉市": 101132300,
        "双河市": 101132400,
        "新星市": 101132500,
        "胡杨河市": 101132600,
        "拉萨": 101140100,
        "日喀则": 101140200,
        "昌都": 101140300,
        "林芝": 101140400,
        "山南": 101140500,
        "那曲": 101140600,
        "阿里地区": 101140700,
        "西宁": 101150100,
        "海东": 101150200,
        "海北藏族自治州": 101150300,
        "黄南藏族自治州": 101150400,
        "海南藏族自治州": 101150500,
        "果洛藏族自治州": 101150600,
        "玉树藏族自治州": 101150700,
        "海西蒙古族藏族自治州": 101150800,
        "兰州": 101160100,
        "定西": 101160200,
        "平凉": 101160300,
        "庆阳": 101160400,
        "武威": 101160500,
        "金昌": 101160600,
        "张掖": 101160700,
        "酒泉": 101160800,
        "天水": 101160900,
        "白银": 101161000,
        "陇南": 101161100,
        "嘉峪关": 101161200,
        "临夏回族自治州": 101161300,
        "甘南藏族自治州": 101161400,
        "银川": 101170100,
        "石嘴山": 101170200,
        "吴忠": 101170300,
        "固原": 101170400,
        "中卫": 101170500,
        "郑州": 101180100,
        "安阳": 101180200,
        "新乡": 101180300,
        "许昌": 101180400,
        "平顶山": 101180500,
        "信阳": 101180600,
        "南阳": 101180700,
        "开封": 101180800,
        "洛阳": 101180900,
        "商丘": 101181000,
        "焦作": 101181100,
        "鹤壁": 101181200,
        "濮阳": 101181300,
        "周口": 101181400,
        "漯河": 101181500,
        "驻马店": 101181600,
        "三门峡": 101181700,
        "济源": 101181800,
        "南京": 101190100,
        "无锡": 101190200,
        "镇江": 101190300,
        "苏州": 101190400,
        "南通": 101190500,
        "扬州": 101190600,
        "盐城": 101190700,
        "徐州": 101190800,
        "淮安": 101190900,
        "连云港": 101191000,
        "常州": 101191100,
        "泰州": 101191200,
        "宿迁": 101191300,
        "武汉": 101200100,
        "襄阳": 101200200,
        "鄂州": 101200300,
        "孝感": 101200400,
        "黄冈": 101200500,
        "黄石": 101200600,
        "咸宁": 101200700,
        "荆州": 101200800,
        "宜昌": 101200900,
        "十堰": 101201000,
        "随州": 101201100,
        "荆门": 101201200,
        "恩施土家族苗族自治州": 101201300,
        "仙桃": 101201400,
        "潜江": 101201500,
        "天门": 101201600,
        "神农架": 101201700,
        "杭州": 101210100,
        "湖州": 101210200,
        "嘉兴": 101210300,
        "宁波": 101210400,
        "绍兴": 101210500,
        "台州": 101210600,
        "温州": 101210700,
        "丽水": 101210800,
        "金华": 101210900,
        "衢州": 101211000,
        "舟山": 101211100,
        "合肥": 101220100,
        "蚌埠": 101220200,
        "芜湖": 101220300,
        "淮南": 101220400,
        "马鞍山": 101220500,
        "安庆": 101220600,
        "宿州": 101220700,
        "阜阳": 101220800,
        "亳州": 101220900,
        "滁州": 101221000,
        "淮北": 101221100,
        "铜陵": 101221200,
        "宣城": 101221300,
        "六安": 101221400,
        "池州": 101221500,
        "黄山": 101221600,
        "福州": 101230100,
        "厦门": 101230200,
        "宁德": 101230300,
        "莆田": 101230400,
        "泉州": 101230500,
        "漳州": 101230600,
        "龙岩": 101230700,
        "三明": 101230800,
        "南平": 101230900,
        "南昌": 101240100,
        "九江": 101240200,
        "上饶": 101240300,
        "抚州": 101240400,
        "宜春": 101240500,
        "吉安": 101240600,
        "赣州": 101240700,
        "景德镇": 101240800,
        "萍乡": 101240900,
        "新余": 101241000,
        "鹰潭": 101241100,
        "长沙": 101250100,
        "湘潭": 101250200,
        "株洲": 101250300,
        "衡阳": 101250400,
        "郴州": 101250500,
        "常德": 101250600,
        "益阳": 101250700,
        "娄底": 101250800,
        "邵阳": 101250900,
        "岳阳": 101251000,
        "张家界": 101251100,
        "怀化": 101251200,
        "永州": 101251300,
        "湘西土家族苗族自治州": 101251400,
        "贵阳": 101260100,
        "遵义": 101260200,
        "安顺": 101260300,
        "铜仁": 101260400,
        "毕节": 101260500,
        "六盘水": 101260600,
        "黔东南苗族侗族自治州": 101260700,
        "黔南布依族苗族自治州": 101260800,
        "黔西南布依族苗族自治州": 101260900,
        "成都": 101270100,
        "攀枝花": 101270200,
        "自贡": 101270300,
        "绵阳": 101270400,
        "南充": 101270500,
        "达州": 101270600,
        "遂宁": 101270700,
        "广安": 101270800,
        "巴中": 101270900,
        "泸州": 101271000,
        "宜宾": 101271100,
        "内江": 101271200,
        "资阳": 101271300,
        "乐山": 101271400,
        "眉山": 101271500,
        "雅安": 101271600,
        "德阳": 101271700,
        "广元": 101271800,
        "阿坝藏族羌族自治州": 101271900,
        "凉山彝族自治州": 101272000,
        "甘孜藏族自治州": 101272100,
        "广州": 101280100,
        "韶关": 101280200,
        "惠州": 101280300,
        "梅州": 101280400,
        "汕头": 101280500,
        "深圳": 101280600,
        "珠海": 101280700,
        "佛山": 101280800,
        "肇庆": 101280900,
        "湛江": 101281000,
        "江门": 101281100,
        "河源": 101281200,
        "清远": 101281300,
        "云浮": 101281400,
        "潮州": 101281500,
        "东莞": 101281600,
        "中山": 101281700,
        "阳江": 101281800,
        "揭阳": 101281900,
        "茂名": 101282000,
        "汕尾": 101282100,
        "东沙群岛": 101282200,
        "昆明": 101290100,
        "曲靖": 101290200,
        "保山": 101290300,
        "玉溪": 101290400,
        "普洱": 101290500,
        "昭通": 101290700,
        "临沧": 101290800,
        "丽江": 101290900,
        "西双版纳傣族自治州": 101291000,
        "文山壮族苗族自治州": 101291100,
        "红河哈尼族彝族自治州": 101291200,
        "德宏傣族景颇族自治州": 101291300,
        "怒江傈僳族自治州": 101291400,
        "迪庆藏族自治州": 101291500,
        "大理白族自治州": 101291600,
        "楚雄彝族自治州": 101291700,
        "南宁": 101300100,
        "崇左": 101300200,
        "柳州": 101300300,
        "来宾": 101300400,
        "桂林": 101300500,
        "梧州": 101300600,
        "贺州": 101300700,
        "贵港": 101300800,
        "玉林": 101300900,
        "百色": 101301000,
        "钦州": 101301100,
        "河池": 101301200,
        "北海": 101301300,
        "防城港": 101301400,
        "海口": 101310100,
        "三亚": 101310200,
        "三沙": 101310300,
        "儋州": 101310400,
        "五指山": 101310500,
        "琼海": 101310600,
        "文昌": 101310700,
        "万宁": 101310800,
        "东方": 101310900,
        "定安": 101311000,
        "屯昌": 101311100,
        "澄迈": 101311200,
        "临高": 101311300,
        "白沙黎族自治县": 101311400,
        "昌江黎族自治县": 101311500,
        "乐东黎族自治县": 101311600,
        "陵水黎族自治县": 101311700,
        "保亭黎族苗族自治县": 101311800,
        "琼中黎族苗族自治县": 101311900,
        "香港": 101320300,
        "澳门": 101330100,
        "台湾": 101341100
    };

    class AutoJob {
        constructor(config) {
            this.config = this._formatConfig(config);
        }

        _formatConfig(config) {
            const newConfig = {};

            if (![0, 1, 2].includes(config.mode)) {
                throw new TypeError('mode 的值必须是 0, 1, 2')
            }
            if (typeof config.city !== 'string') {
                throw new TypeError('city 类型必须是 string')
            }
            if (typeof config.keyword !== 'string') {
                throw new TypeError('keyword 类型必须是 string')
            }
            if (typeof config.message !== 'string') {
                throw new TypeError('message 类型必须是 string')
            }
            if (!config.message.length) {
                throw new TypeError('message 不能为空')
            }
            if (config.salary !== undefined) {
                if (typeof config.salary !== 'number' && !Array.isArray(config.salary)) {
                    throw new TypeError('salary 类型必须是 number 或 array')
                }
                if (
                    Array.isArray(config.salary) &&
                    config.salary.length &&
                    (typeof config.salary[0] !== 'number' || typeof config.salary[1] !== 'number')
                ) {
                    throw new TypeError('salary 类型为数组时前两项必须是 number 类型')
                }
            }
            newConfig.mode = config.mode;
            newConfig.city = config.city;
            newConfig.keyword = config.keyword;
            newConfig.otherPlace = !!config.otherPlace;
            newConfig.excludeKeywords = Array.isArray(config.excludeKeywords) ? config.excludeKeywords : [];
            newConfig.experience = Array.isArray(config.experience) ? config.experience : [];
            newConfig.liveness = Array.isArray(config.liveness) ? config.liveness : [];
            newConfig.excludes = Array.isArray(config.excludes) ? config.excludes : [];
            newConfig.scale = Array.isArray(config.scale) ? config.scale : [];
            newConfig.degree = Array.isArray(config.degree) ? config.degree : [];
            newConfig.min = (typeof newConfig.min === 'number' ? newConfig.min : 3) * 1000;
            newConfig.max = (typeof newConfig.max === 'number' ? newConfig.max : 6) * 1000;
            newConfig.message = config.message;
            newConfig.salary = config.salary ?? [];

            return newConfig
        }

        start() {
            if (window.location.pathname === '/web/geek/job') { // 通过搜索框打开的 jobs
                this._traverseJob();
            } else if (window.location.pathname === '/web/geek/recommend') { // 推荐职位的 jobs
                this._traverseRecommend();
            }
            else if (window.location.pathname.indexOf('/job_detail') === 0) { // 详情页
                this._checkValidJob();
            } else if (window.location.pathname === '/web/geek/chat') { // 聊天页
                this._sayHello();
            }
            else {
                this._toJobs();
            }
        }

        /**
         * 首页操作
         * 1、打开推荐职位
         * 2、选择城市并所搜职位关键词
         */
        async _toJobs() {
            const { config } = this;

            // 选择城市并所搜职位关键词
            if (config.mode === 0) {
                const nav = document.querySelector('.nav-city-box');
                const selected = nav.querySelector('.nav-city-selected');
                if (selected.innerText !== config.city) {
                    nav.click();
                    const section = await monitorElementGeneration('.city-group-section');
                    const citys = section.querySelectorAll('a');
                    const targetCity = Array.from(citys).find(city => city.innerText === config.city);
                    if (window.location.pathname !== targetCity.pathname) {
                        targetCity.click();
                    }
                    return
                }
                // 填写职位关键词
                const form = document.querySelector('.search-form');
                const search = form.querySelector('.search-form-con > .ipt-wrap > input');
                search.value = config.keyword;
                const button = form.querySelector('.btn-search');
                button.click();
            } else {
                // 打开推荐职位
                const recommend = await monitorElementGeneration('.merge-city-job-recommend');
                const moreBtn = recommend.querySelector('.common-tab-more > a');
                moreBtn.click();
            }
        }

        /**
         * 修正获取的 jobs 并逐个访问
         */
        async _traverseJob() {
            const { config } = this;

            const url = new URL(window.location.href);
            const { searchParams } = url;

            if (!searchParams.has('page')) {
                searchParams.append('page', 1);
            }
            const page = ~~searchParams.get('page');
            // 限制最多 10 页
            if (page > 10) return

            let isModify = false;
            setSearchParams('city', cityCodeMap[config.city]);
            setSearchParams('query', config.keyword);
            setSearchParams('experience', config.experience);
            setSearchParams('scale', config.scale);
            setSearchParams('degree', config.degree);
            if (Array.isArray(config.salary) && config.salary.length) {
                setSearchParams('salary', -40001);
                setSearchParams('lowSalary', config.salary[0]);
                setSearchParams('highSalary', config.salary[1]);
            } else {
                setSearchParams('salary', config.salary);
            }
            searchParams.set('page', page);
            if (isModify) {
                window.location.search = searchParams.toString();
                return
            }

            await this._traverse();

            searchParams.set('page', page + 1);
            window.location.search = searchParams.toString();

            function setSearchParams(key, value) {
                const oldValue = searchParams.get(key);
                if (oldValue == null || oldValue.toString() !== value.toString()) {
                    searchParams.set(key, value);
                    isModify = true;
                }
            }
        }

        /**
         * 修正获取的 jobs 并逐个访问
         */
        async _traverseRecommend() {
            const { config } = this;

            const url = new URL(window.location.href);
            const { searchParams } = url;

            if (!searchParams.has('page')) {
                searchParams.append('page', 1);
            }
            const page = ~~searchParams.get('page');
            // 限制最多 30 页
            if (page > 30) return

            const cities = await monitorElementsGeneration('.system-search-condition .expect-list > .expect-item');
            const city = Array.from(cities).find(city => city.innerText.includes(config.city));
            if (!city) return

            city.click();

            const jobTabs = await monitorElementsGeneration('.user-jobs-area .job-tab > span');
            Array.from(jobTabs).find(tab => tab.innerText === (config.mode === 2 ? '最新职位' : '精选职位')).click();

            let isModify = false;
            setSearchParams('scale', config.scale);
            setSearchParams('degree', config.degree);
            setSearchParams('experience', config.experience);
            const newUrl = new URL(window.location.href);
            const { searchParams: newSearchParams } = newUrl;
            searchParams.set('expectId', newSearchParams.get('expectId'));
            searchParams.set('sortType', newSearchParams.get('sortType'));
            if (Array.isArray(config.salary) && config.salary.length) {
                setSearchParams('salary', -40001);
                setSearchParams('lowSalary', config.salary[0]);
                setSearchParams('highSalary', config.salary[1]);
            } else {
                setSearchParams('salary', config.salary);
            }
            if (isModify) {
                window.location.search = searchParams.toString();
                return
            }

            await this._traverse();

            searchParams.set('page', page + 1);
            window.location.search = searchParams.toString();

            function setSearchParams(key, value) {
                const oldValue = searchParams.get(key);
                if (oldValue == null || oldValue.toString() !== value.toString()) {
                    searchParams.set(key, value);
                    isModify = true;
                }
            }
        }

        /**
         * 遍历 jobs
         */
        async _traverse() {
            const { config } = this;
            const box = await monitorElementGeneration('.job-list-box');
            const handlers = Array.from(box.querySelectorAll('.job-card-wrapper'))
                .filter(dom => {
                    const isfriend = dom.querySelector('.job-card-left > .job-info > .start-chat-btn');
                    const name = dom.querySelector('.job-card-right .company-name > a');
                    const jobName = dom.querySelector('.job-card-left .job-name');
                    // 过滤已沟通的职位
                    return isfriend.innerText === '立即沟通' &&
                        // 排除的公司
                        !config.excludes.some(exclude => name.innerText.includes(exclude)) &&
                        // 是否接受外地职位
                        (config.otherPlace || !dom.querySelector('.job-card-left > .icon-other-place')) &&
                        // 职位名称匹配
                        jobName.innerText.includes(config.keyword) &&
                        // 职位名称排除关键词
                        !config.excludeKeywords.find(keyword => jobName.innerText.includes(keyword))
                })
                .map(dom => {
                    return () => new Promise(resolve => {
                        setTimeout(() => {
                            dom.click();
                            resolve();
                        }, random(config.min, config.max));
                    })
                });

            for (const handler of handlers) {
                await handler();
            }
        }

        /**
         * 检查 job 是否符合,符合则打招呼
         */
        async _checkValidJob() {
            const { config } = this;
            const info = await monitorElementGeneration('.job-boss-info>.name');
            const liveness = info.querySelector('span');
            if (!liveness || (config.liveness.length && !config.liveness.includes(liveness.innerText))) {
                this._close();
                return
            }
            const commentBtn = await monitorElementGeneration('.job-banner .btn-container :nth-child(2)');
            if (commentBtn.dataset.isfriend === 'true') {
                this._close();
                return
            }
            commentBtn.click();
            const message = await monitorElementGeneration('.dialog-container>.dialog-con>.startchat-content .edit-area');
            const input = message.querySelector('.input-area');
            const send = message.querySelector('.send-message');
            const inputEv = new Event('input', { bubbles: true });
            inputEv.simulated = true;
            input.value = config.message;
            input.dispatchEvent(inputEv);

            setTimeout(() => {
                send.click();

                this._close();
            }, 1000);
        }

        /**
         * 点击立即沟通后有可能直接打开沟通页面,此时需要在当前页发招呼语
         */
        async _sayHello() {
            const content = await monitorElementGeneration('.chat-conversation>.message-content');
            const controls = await monitorElementGeneration('.chat-conversation>.message-controls');
            const mySelf = content.querySelectorAll('.chat-message .item-myself');
            // 如果有,说明该对话框发送过消息,视为已打过招呼,直接返回
            if (mySelf.length) {
                this._close();
                return
            }
            const input = controls.querySelector('.chat-editor #chat-input');
            const send = controls.querySelector('.chat-editor .btn-send');
            const inputEv = new Event('input', { bubbles: true });
            inputEv.simulated = true;
            input.innerText = config.message;
            input.dispatchEvent(inputEv);

            setTimeout(() => {
                send.click();

                this._close();
            }, 1000);
        }

        _close() {
            setTimeout(() => {
                window.close();
            }, 3000);
        }
    }

    return AutoJob;

})();
new AutoJob(config).start()