showBossActiveTime

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

目前為 2023-04-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

(function () {
  'use strict';

  class ShowBossActiveTime {
    constructor(options) {
      this.isUpdating = false;
      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('开始更新状态....,网站安全策略问题,更新会比较缓慢。');

      this.isUpdating = true;
      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 (this.queque.length === 0) {
        this.isUpdating = false;
        this.alertBox('查询完毕,更新即将完成');
      }
    }
    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);
                  }
                );
              } 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);
              }
            }
          });
        };
      } 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);
            })
            .catch(async (error) => {
              /*请求被302临时重定向了,无法获取到数据,需要用iframe来获取了*/
              this.getStatusByIframe(link, index).then((text) => {
                if (text === '') {
                  text = '未知状态';
                }
                this.setText(node, text, chat);
              });
            });
        };
      }
    }
    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);
    }
    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;
      }
    }
    toggleChated() {
      this.list.forEach((node) => {
        const chat = node.querySelector(this.options.chatElement).textContent;
        if (this.options.hideChated && chat === '继续沟通') {
          node.style.display = 'none';
        } else {
          node.style.display = 'block';
        }
      });
    }
    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);
      });

      const loopDom = () => {
        this.list.forEach((node) => {
          const status = node.querySelector('.status')?.textContent;
          const chat = node.querySelector('.chat')?.textContent;
          if (status && chat) {
            if (this.removeStatusList.includes(status)) {
              node.style.display = 'none';
            } else {
              node.style.display = 'block';
            }
            if (this.options.hideChated && chat === '继续沟通') {
              node.style.display = 'none';
            }
          }
        });
      };

      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;
        loopDom();
        // 如果正在更新,需要定时器定时查询最新数据,不然后面更新的数据不能触发loopDom
        if (this.isUpdating) {
          const timer = setInterval(() => {
            // 如果已经更新完毕,清除定时器,这里面多次选择会触发多个定时器,可以把timer提到外面避免这个问题,但因为实际使用中,重复调用的代价并不明显,也因为懒,就不提出到外面了
            if (!this.isUpdating) {
              clearInterval(timer);
            }
            loopDom();
          }, 1000);
        }
      });

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

    init() {
      // 获取列表数据
      this.getList();
      // 判断是否要隐藏已经沟通过的数据
      this.toggleChated();
      // 先给在线的数据设置状态
      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.getListStatus();
    }
  }
  function start() {
    const Lis = document.querySelectorAll('.job-card-wrapper');
    if (Lis.length) {
      new ShowBossActiveTime();
    } else {
      console.log('no start');
      setTimeout(start, 2000);
    }
  }
  start();
})();