showBossActiveTime

to show hr lastest login time,help you deliver your resume efficiently.

当前为 2023-05-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         showBossActiveTime
// @namespace    http://www.chensong.cc/
// @version      0.4.1
// @description  to show hr lastest login time,help you deliver your resume efficiently.
// @author       chensong
// @match        https://www.zhipin.com/web/geek/job
// @match        https://www.zhipin.com/web/geek/job*
// @match        https://www.zhipin.com/web/geek/recommend
// @match        https://www.zhipin.com/web/geek/recommend*
// @exclude      https://www.zhipin.com/web/geek/map/jobs*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant       GM_xmlhttpRequest
// @license     MIT
// ==/UserScript==

(function () {
  'use strict';

  class ShowBossActiveTime {
    constructor(options) {
      this.startTime = null;//记录是否是当前列表数据的循环,如果没查询完毕就重新触发的新的实例化,需要去停止旧的查询循环
      this.statusOptions = localStorage
        .getItem('bossActiveStatusList')
        ?.split(',') || [
        '半年前活跃',
        '近半年活跃',
        '4月前活跃',
        '2月内活跃',
        '2周内活跃'
      ];
      this.removeStatusList = [];
      this.options = Object.assign(
        {
          listElement: '.job-card-wrapper',
          onlineElement: '.boss-online-tag',
          chatElement: '.start-chat-btn',
          hunterElement: '.job-tag-icon',
          linkElement: '.job-card-left',
          paginationElement: '.options-pages',
          hideChated: false
        },
        options
      );
      this.queque = []; //查询队列
      this.list = []; //数据列表
      //添加过滤条件,因为要保存选择数据,所以这个不能切换时清空
      this.addStatusFilter();
      this.addStyleSheet();
      // 监听请求数据事件
      this.observeLoadingData();
      this.request = this.requestInit();
      this.init();
    }

    addStyleSheet() {
      const style = `
            .show-active-status{display:flex;padding:5px 10px;background:#e1f5e3;color:green;width:80%;border-radius:4px;margin-top:10px;}
            .show-active-status .status{}
            .show-active-status .chat{}
            #alertBox{position: fixed; top: 20%; left: 50%; transform: translate(-50%, -50%); background-color: rgb(0 190 189); border-radius: 5px; color: #fff; z-index: 9999; padding: 20px 40px; font-size: 20px; box-shadow: 0px 0px 10px rgba(0,0,0,.2);}
            #removeFilterDataContainer{
            position: fixed;right: 70px;top: 70px;z-index: 20000;background: #00bebd; color: #fff;display: flex;flex-direction: column;padding-bottom:10px
            }
            #removeFilterDataContainer.hide{height:28px;overflow:hidden}
            #removeFilterDataContainer .title {display:flex;justify-content: space-around;}
            #removeFilterDataContainer .title label{align-items:center;padding:0 15px;}
            #removeFilterDataContainer.hide #boss-active-time-arrow svg{transform: rotate(180deg);}
            #removeFilterDataContainer #boss-active-time-arrow {cursor: pointer;font-size: 24px;background: #009796;padding:2px 10px;line-height:1;}
            #removeFilterDataContainer .tips{font-size:16px;margin:5px 20px;}
            #removeFilterDataContainer label{display:flex;padding:0 20px;}
            #removeFilterDataContainer label input{margin-right:5px;}
            `;
      const styleEle = document.createElement('style');
      styleEle.id = 'show-boss-active-time-css';
      styleEle.innerHTML = style;
      document.head?.appendChild(styleEle);
    }

    // 获取节点列表
    getList() {
      Array.from(document.querySelectorAll(this.options.listElement)).forEach(
        (node, index) => {
          const status = node.querySelector(this.options.onlineElement);
          this.list.push(node);
          // 不在线
          if (!status) {
            this.queque.push(node);
          }
        }
      );
    }
    // 设置文本内容
    setText(node, text, status) {
      const html = `
        <div class="show-active-status">
          <p class="status">${text}</p>&nbsp;&nbsp;&nbsp;&nbsp;
          <p class="chat">${status}</p>
        </div>
      `;
      node.querySelector('.job-info').insertAdjacentHTML('afterend', html);
      let aEle = node.querySelector('a');
      aEle.style.height = 'auto';
      aEle.style.paddingBottom = '0';
      if(!this.statusOptions.includes(text)&&text!=='在线'){
        this.statusOptions.push(text);
        localStorage.setItem('bossActiveStatusList',this.statusOptions);
      }
    }
    async getListStatus() {
      this.alertBox('开始更新状态....,网站安全策略问题,更新会比较缓慢。');
      const startTime = new Date().getTime();
      this.startTime = startTime;
      for (let i = 0; this.queque.length > 0; i++) {
        let node = this.queque.shift();
        let link = node.querySelector(this.options.linkElement).href;
        let chat = node.querySelector(this.options.chatElement).textContent;
        await new Promise((resolve) => {
          setTimeout(async () => {
            /*做个延时处理,频繁请求会触发302重定向,最终导致拿不到html页面数据*/
            await this.request(link, node, chat, this.queque.length);
            resolve();
          }, 1000);
        });
        if(startTime!==this.startTime){
          // 中断旧的循环查询
          return;
        }
      }
      if (this.queque.length === 0) {
        this.startTime = null;
        setTimeout(()=>{
          this.alertBox('查询完毕,更新即将完成');
        },1500);
      }
    }
    requestInit(){
      //如果是油猴,使用GM_xmlhttpRequest,如果不是,使用fetch
      if (window.GM_xmlhttpRequest) {
        return (link, node, chat, index) => {
          return GM_xmlhttpRequest({
            method: 'GET',
            url: link,
            onload: (response) => {
              if (/security-check.html/.test(response.finalUrl)) {
                //    用GM_xmlhttpRequest获取触发了302,用finaleUrl通过iframe来获取,不用link,看是否能省略302这个步骤,加快速度
                this.getStatusByIframe(response.finalUrl, index).then(
                  (text) => {
                    if (text === '') {
                      text = '未知状态';
                    }
                    this.setText(node, text, chat);
                    this.toggleDom(node);
                  }
                );
              } else {
                const html = response.responseText;
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const text = this.getStatusText(doc);
                this.setText(node, text, chat);
                this.toggleDom(node);
              }
            }
          });
        };
      } else {
        return (link, node, chat, index) => {
          /*设置不允许重定向,让其报错,报错后通过iframe来获取数据,虽然慢点,但起码可以获取到数据*/
          return fetch(link, { redirect: 'error' })
            .then((res) => {
              return res.text();
            })
            .then(async (data) => {
              const doc = document.createElement('div');
              doc.insertAdjacentHTML('afterbegin', data);
              const text = this.getStatusText(doc);
              this.setText(node, text, chat);
              this.toggleDom(node);
            })
            .catch(async (error) => {
              /*请求被302临时重定向了,无法获取到数据,需要用iframe来获取了*/
              this.getStatusByIframe(link, index).then((text) => {
                if (text === '') {
                  text = '未知状态';
                }
                this.setText(node, text, chat);
                this.toggleDom(node);
              });
            });
        };
      }
    }
    async getStatusByIframe(link, id) {
      let iframe = document.createElement('iframe');
      iframe.src = link;
      iframe.id = 'tempIframe' + id;
      iframe.style.cssText = 'width:0;height:0;';
      document.body.appendChild(iframe);

      return await new Promise((resolve) => {
        let tempIframe = document.querySelector('#tempIframe' + id);
        tempIframe.onload = () => {
          setTimeout(() => {
            if (tempIframe.contentWindow?.document) {
              const text = this.getStatusText(
                tempIframe.contentWindow.document
              );
              resolve(text);
              console.log('用iframe获取', text);
              setTimeout(() => {
                document.body.removeChild(tempIframe);
              }, 500);
            }
          }, 5000);
        };
      });
    }
    observeLoadingData() {
      const container = document.querySelector('.search-job-result');
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === 'childList') {
            const addNode = mutation.addedNodes;
            const removedNode = mutation.removedNodes;
            if (
              addNode.length &&
              addNode[0].className === 'job-loading-wrapper'
            ) {
              console.log('触发了请求列表数据');
            }
            if (
              removedNode.length &&
              removedNode[0].className === 'job-loading-wrapper'
            ) {
              console.log('加载完成');
              this.clear();
              this.init();
            }
          }
        });
      });
      const config = { attributes: false, childList: true, subtree: false };
      observer.observe(container, config);

      // 监听是否是搜索列表页,不是就移除dom
      const listContainer = document.querySelector('#wrap');
      const listObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === 'childList') {
            const wrapper = document.querySelector('.job-list-wrapper');
            const removeNode = document.querySelector("#removeFilterDataContainer");
            if(!wrapper&&removeNode){
              document.body.removeChild(removeNode);
              listObserver.disconnect();
              // 清除查询
              this.clear();
            }
          }
        });
      });
      listObserver.observe(listContainer, config);
    }
    alertBox(msg) {
      let div = document.createElement('div');
      div.id = 'alertBox';
      div.innerHTML = msg;
      document.body.appendChild(div);
      setTimeout(function () {
        document.body.removeChild(div);
      }, 2000);
    }
    getStatusText(doc) {
      const timeNode = doc.querySelector('.boss-active-time');
      if (timeNode) {
        return timeNode.textContent;
      } else {
        // 没有获取到状态,但页面是已经加载到的了
        const isHunter = ['.certification-tags', '.boss-info-attr'].filter(
          (name) => {
            const node = doc.querySelector(name);
            return /猎头|人力|经纪/.test(node?.textContent);
          }
        );
        const status = isHunter
          ? '猎头,没有活跃状态'
          : '获取到数据了,但不知道是什么数据';
        return status;
      }
    }
    toggleDom(node){
      const status = node.querySelector('.status')?.textContent;
      const chat = node.querySelector(this.options.chatElement).textContent;
      // 先显示全部
      node.style.display = 'block';
      // 首先判断是否隐藏已沟通
      if (this.options.hideChated && chat === '继续沟通') {
        node.style.display = 'none';
      }
      // 状态数据已经获取了
      if (status && chat) {
        if (this.removeStatusList.includes(status)) {
          node.style.display = 'none';
        }
        if (this.options.hideChated && chat === '继续沟通') {
          node.style.display = 'none';
        }
      }
    }
    toggleDoms(){
      this.list.forEach((node) => {
        this.toggleDom(node);
      });
    }
    addStatusFilter() {
      const container = document.createElement('div');
      container.id = 'removeFilterDataContainer';
      const html = `
      <label><input type="checkbox" name="hideChated" value="1">过滤已经沟通过的</label>
      <div id="boss-active-time-arrow"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path></svg></div>
      `;
      const title = document.createElement('div');
      title.className = 'title';
      title.innerHTML = html;
      const tips = document.createElement('div');
      tips.innerHTML = '过滤掉勾选的数据';
      tips.className = 'tips';
      container.appendChild(title);
      container.appendChild(tips);
      container
        .querySelector('#boss-active-time-arrow')
        .addEventListener('click', function () {
          container.classList.contains('hide')
            ? container.classList.remove('hide')
            : container.classList.add('hide');
        });

      this.statusOptions.forEach((option) => {
        const label = document.createElement('label');
        const el = document.createElement('input');
        el.type = 'checkbox';
        el.name = option;
        el.value = option;
        el.className = 'status-checkbox';
        label.appendChild(el);
        label.appendChild(document.createTextNode(option));
        container.appendChild(label);
      });

      container.addEventListener('change', () => {
        const selectedValues = Array.from(
          container.querySelectorAll('.status-checkbox:checked')
        ).map((el) => el.value);
        this.removeStatusList = selectedValues;
        const hideNode = document.querySelector('input[name="hideChated"]');
        this.options.hideChated = hideNode?.checked;
        this.toggleDoms();
      });

      document.body.appendChild(container);
    }
    clear(){
      this.queque.length = 0;
      this.list.length = 0;
      this.startTime = null;
    }

    init() {
      // 获取列表数据
      this.getList();
      // 先给在线的数据设置状态
      this.list.forEach((node) => {
        const chat = node.querySelector(this.options.chatElement).textContent;
        const online = node.querySelector(this.options.onlineElement);
        if (online) {
          this.setText(node, '在线', chat);
        }
      });
      // 判断是否要隐藏已经沟通过的数据
      this.toggleDoms();
      // 请求数据,给不在线的设置状态
      this.getListStatus();
    }
  }
  function start() {
    const Lis = document.querySelectorAll('.job-card-wrapper');
    if (Lis.length) {
      new ShowBossActiveTime();
    } else {
      console.log('no start');
      setTimeout(start, 2000);
    }
  }
  start();
})();