Greasy Fork is available in English.

jkforum helper

捷克论坛助手:自动签到、定时签到、自动感谢、自动加载原图、自动支付购买主题贴、自动完成投票任务,优化浏览体验,一键批量回帖/感谢,一键打包下载帖子图片

Od 22.06.2021.. Pogledajte najnovija verzija.

// ==UserScript==
// @name         jkforum helper
// @namespace    https://github.com/Eished/jkforum_helper
// @version      0.4.3
// @description  捷克论坛助手:自动签到、定时签到、自动感谢、自动加载原图、自动支付购买主题贴、自动完成投票任务,优化浏览体验,一键批量回帖/感谢,一键打包下载帖子图片
// @author       Eished
// @license      AGPL-3.0
// @match        *://*.jkforum.net/*
// @exclude      *.jkforum.net/member*
// @icon         https://www.google.com/s2/favicons?domain=jkforum.net
// @require      https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js
// @require      https://cdn.jsdelivr.net/npm/jszip@3.6.0/dist/jszip.min.js
// @connect      mymypic.net
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @grant        GM_info
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
  'use strict';
  if (document.querySelector('.listmenu li a')) {
    function newUser(username, formhash) {
      const user = {
        username: username,
        formhash: formhash,
        version: GM_info.script.version,
        today: '', // 签到日期
        signtime: '23:59:59', // 签到时间
        signNum: 10, // 签到重试次数
        interTime: 200, // 签到重试间隔时间
        todaysay: '簽到', // 签到输入内容
        mood: 'fd', // 签到心情
        differ: 10000, // 回帖随机间隔时间
        interval: 20000, // 回帖基础间隔时间
        autoPaySw: 1, // 自动支付开关
        autoThkSw: 1, // 自动感谢开关
        autoRePicSw: 1, // 自动加载原图开关
        page: '', // 批量回帖页码
        votedMessage: '+1', //投票输入内容
        userReplyMessage: [], // 用户保存的回复,历史回帖内容
        replyMessage: [], // 用于回复的内容,临时回帖内容
        fastReply: [], // 保存的快速回复,快速回帖内容
        replyThreads: [], // 回帖数据
        applyVotedUrl: 'https://www.jkforum.net/home.php?mod=task&do=apply&id=59',
        vidUrl: '',
        votedUrl: 'https://www.jkforum.net/plugin.php?',
        taskDoneUrl: 'https://www.jkforum.net/home.php?mod=task&do=draw&id=59',
        signUrl: 'https://www.jkforum.net/plugin/?id=dsu_paulsign:sign&operation=qiandao&infloat=1&inajax=1',
        thkUrl: 'https://www.jkforum.net/plugin/?id=thankauthor:thank&inajax=1',
        payUrl: 'https://www.jkforum.net/forum.php?mod=misc&action=pay&paysubmit=yes&infloat=yes&inajax=1',
        fastReplyUrl: '',
        replyUrl: "https://www.jkforum.net/forum.php?mod=post&action=reply&",
      }
      return user;
    }
    async function creatUser() {
      const formhash = document.querySelector('.listmenu li a').href.split('&')[2].split('=')[1];
      const username = document.querySelector('.avatar_info').querySelector('a').innerHTML;
      let user = getUserFromName();
      if (!user) { // 空则写入,或版本变动写入
        user = newUser(username, formhash);
        user = await setFastReply(user); // 异步设置快速回复
        GM_setValue(username, user);
        messageBox("添加用户成功!");
        console.log("添加用户成功!");
      } else if (user.version != GM_info.script.version) {
        const userMod = newUser(username, formhash);
        const compa = compaObjKey(userMod, user); // 比较key
        if (compa) { // key相同 只改变版本
          user.version = GM_info.script.version; // 记录新版本
        } else { // key不同
          user.version = GM_info.script.version; // 记录新版本
          user = copyObjVal(userMod, user); // 对newUser赋值
        }
        messageBox("版本更新成功!请阅读使用说明。");
        console.log("版本更新成功!");
        user = await setFastReply(user); // 异步设置快速回复
        GM_setValue(username, user); // 先存储,防止异步错误
      }
      if (user.formhash != formhash) { //formhash 变动存储
        user.formhash = formhash;
        GM_setValue(username, user);
      }

      launch(user); // 启动自动签到、投票
    }

    function getUserFromName() { //从用户名获取对象
      const username = document.querySelector('.avatar_info').querySelector('a').innerHTML; // 用户名判断唯一用户
      return GM_getValue(username);
    }

    async function setFastReply(user) { //设置快速回复
      const fastReplyUrl = 'https://www.jkforum.net/thread-8364615-1-1.html'; // 获取快速回复的地址
      user.fastReplyUrl = fastReplyUrl; // 设置链接用于异步校验
      GM_setValue(user.username, user);
      const htmlData = await getData(fastReplyUrl);
      const options = htmlData.querySelectorAll('#rqcss select option');
      let fastReply = []; //返回数组
      options.forEach(option => {
        if (option.outerText) { //去掉空值
          fastReply.push(replaceHtml(option.value)); //去掉需要转义的内容
        }
      });
      user.fastReply = fastReply;
      messageBox("获取快速回复成功!");
      console.log("获取快速回复成功!");
      return user;
    }

    async function launch(user) {
      rePic(user); // 启动自动加载原图,自动感谢等;
      if (user.username) { //验证是否登录 //天变动则签到
        if (user.today != nowTime('day')) {
          user.today = nowTime('day');

          await getData(user.applyVotedUrl); // 申请任务
          messageBox("申请投票任务执行成功!");
          console.log("申请投票任务执行成功!");

          const searchParamsUrl = new URLSearchParams();
          searchParamsUrl.append("id", "voted"); // 投票请求
          const htmlData = await getData(user.votedUrl + searchParamsUrl.toString());
          const vidUrl = htmlData.querySelector('.voted a').href; // 找到链接
          user.vidUrl = vidUrl;

          const hrefHtmlData = await getData(vidUrl);
          const vid = user.vidUrl.split('&')[2].split('=')[1]; // 纯数字// 分解链接
          const aidUrl = hrefHtmlData.querySelector('.hp_s_c a').href; // 找到链接
          const aid = aidUrl.split('&')[2].split('=')[1]; // 纯数字// 分解链接
          // 投票请求和数据
          searchParamsUrl.append("ac", "dian");
          searchParamsUrl.append("aid", aid);
          searchParamsUrl.append("vid", vid);
          searchParamsUrl.append("qr", "");
          searchParamsUrl.append("inajax", 1);
          const searchParamsData = new URLSearchParams();
          searchParamsData.append("formhash", user.formhash);
          searchParamsData.append("inajax", 1);
          searchParamsData.append("handlekey", "dian");
          searchParamsData.append("sid", 0);
          searchParamsData.append("message", user.votedMessage);

          const votedMessage = await postDataAs(user.votedUrl + searchParamsUrl.toString(), searchParamsData.toString());
          console.log(votedMessage);
          if (checkHtml(votedMessage)) {
            let info = '';
            if (votedMessage.querySelector('.alert_info')) {
              info = votedMessage.querySelector('.alert_info').innerHTML; // 解析html,返回字符串,失败警告
            } else if (votedMessage.querySelector('script')) {
              info = votedMessage.querySelector('script').innerHTML.split(`', `)[1].slice(1); // 解析html,获取字符串,成功消息
              messageBox(info);
              console.log(info);
              await getData(getUserFromName().taskDoneUrl); // 执行领奖励
              messageBox('领取投票奖励成功!');
              console.log('领取投票奖励成功!');
            } else {
              info = "投票返回HTML数据识别失败: " + votedMessage;
            }
            messageBox(info, 10000);
          } else {
            messageBox(votedMessage); //其它情况直接输出
          }

          sign(user); // 签到
          GM_setValue(user.username, user); //保存当天日// today 初始化
        }
      } else {
        messageBox('未登录');
      }
    }

    function rePic(user) {
      if (window.location.href.match('thread')) {
        if (user.autoThkSw) { // 自动感谢当前贴开关
          thankThread(user); // 自动感谢当前贴
        }
        if (user.autoPaySw) { // 自动购买当前贴开关
          autoPay(user); // 自动购买当前贴
        }
        const tfImg = document.querySelectorAll('.t_f ignore_js_op img'); //获取图片列表,附件也是ignore_js_op
        if (tfImg && user.autoRePicSw) { // 加载原图开关
          for (let i = 0; i < tfImg.length; i++) { //遍历图片列表
            const img = tfImg[i]
            img.setAttribute('onmouseover', null); // 去掉下载原图提示
            if (img.src.match('.thumb.')) { // 去掉缩略图 加载部分
              console.log('thumb:', img.src);
              img.src = img.getAttribute('file').split('.thumb.')[0];
              messageBox('加载原图成功', 1000)
            } else if (img.src.match('static/image/common/none.gif')) { // 懒加载部分
              img.setAttribute('file', img.getAttribute('file').split('.thumb.')[0]); // 网站自带forum_viewthread.js  attachimgshow(pid, onlyinpost) 从file延迟加载
              console.log('none.gif:', img.src);
              messageBox('加载原图成功', 1000)
            }
          }
        }
      }
    }

    function downloadImgs() {
      let imgsUrl = []; // 图片下载链接
      let imgsTitles = []; // 图片名称
      const folderName = document.querySelector('.title-cont h1').innerHTML.trim().replace(/\.+/g, '-');
      const pcbImg = document.querySelectorAll('.pcb img'); // 所有帖子楼层的图片,逐个过滤
      if (pcbImg.length) {
        for (let i = 0; i < pcbImg.length; i++) { //遍历图片列表
          let img = pcbImg[i];
          if (img.title && img.getAttribute('file') && img.getAttribute('file').match('mymypic.net')) {
            imgsTitles.push(img.title); // 保存下载名称到数组
            imgsUrl.push(img.getAttribute('file').split('.thumb.')[0]); // 保存下载链接到数组
          } else if (!img.getAttribute('file') && img.src.match('mymypic.net')) {
            const nameSrc = img.src.split('/');
            imgsTitles.push(nameSrc[nameSrc.length - 1]); // 保存下载名称到数组
            imgsUrl.push(img.src.split('.thumb.')[0]); // 保存下载链接到数组
          } else {
            // console.log(img.src, '跨域请求,不可下载外链图片!');
            // messageBox('跨域请求,不可下载外链图片!');
          }
        }
        if (imgsUrl.length && imgsTitles.length) {
          batchDownload(imgsUrl, imgsTitles, folderName);
        } else {
          console.log('没有可下载的图片!');
          messageBox('没有可下载的图片!');
          return 0;
        }
      } else {
        console.log('没有图片!');
        messageBox('没有图片!');
        return 0;
      }
    }

    // 批量下载 顺序
    async function batchDownload(imgsUrl, imgsTitles, folderName) {
      const data = imgsUrl;
      const zip = new JSZip();
      const cache = {}
      const promises = []
      console.log(data.length + "张:开始下载...");
      messageBox(data.length + "张:开始下载...");
      for (let index = 0; index < data.length; index++) {
        const item = data[index];
        const promise = await getData(item, "blob").then(data => { // 下载文件, 并存成ArrayBuffer对象
          const file_name = imgsTitles[index]; // 获取文件名
          zip.file(file_name, data, {
            binary: true
          }) // 逐个添加文件
          cache[file_name] = data;
          console.log(`第${index+1}张,文件名:${file_name},大小:${parseInt(data.size / 1024)} Kb,下载完成!等待压缩...`);
          messageBox(`第${index+1}张,文件名:${file_name},大小:${parseInt(data.size / 1024)} Kb,下载完成!等待压缩...`);
        })
        promises.push(promise);
      }
      Promise.all(promises).then(() => {
        zip.generateAsync({
          type: "blob"
        }).then(content => { // 生成二进制流
          saveAs(content, `${folderName} [${data.length}P]`) // 利用file-saver保存文件
        })
      })
    };

    async function autoPay(user) {
      if (document.querySelector('.viewpay')) {
        const url = user.payUrl;
        const referer = location.href;
        const tid = referer.split('-')[1];
        const pData = `formhash=${user.formhash}&referer=${turnUrl(referer)}&tid=${tid}&handlekey=pay`
        const stringOrHtml = await postDataAs(url, pData);
        if (checkHtml(stringOrHtml)) { // 确认html
          const info = stringOrHtml.querySelector('script').innerHTML.split(`', `)[1].slice(1);
          messageBox(info);
          location.reload();
        } else {
          messageBox(stringOrHtml); //其它情况直接输出
        }
      }
    }

    async function thankThread(user) {
      if (document.querySelector('#thankform') && document.querySelectorAll('#k_thankauthor')[1]) { //感谢可见
        await thankThreadPost(user);
        location.reload();
      } else if (document.querySelector('#thankform') && document.querySelectorAll('#k_thankauthor')[0]) { //普通贴
        await thankThreadPost(user);
      }
    };

    async function thankThreadPost(user) {
      const thankform = document.querySelector('#thankform');
      const tid = thankform.querySelector('[name=tid]').value;
      const touser = thankform.querySelector('[name=touser]').value;
      const touseruid = thankform.querySelector('[name=touseruid]').value;
      var searchParams = new URLSearchParams();
      searchParams.append("formhash", user.formhash);
      searchParams.append("tid", tid);
      searchParams.append("touser", touser);
      searchParams.append("touseruid", touseruid);
      searchParams.append("handlekey", "k_thankauthor");
      searchParams.append("addsubmit", "true");
      const xmlData = await postDataAs(user.thkUrl, searchParams.toString()); //post感谢数据
      if (checkHtml(xmlData)) {
        const info = xmlData.querySelector('.alert_info').innerHTML.split('<')[0].trim(); //去除html,返回字符串
        messageBox(info);
        console.log(info);
      } else {
        messageBox(xmlData); //其它情况直接输出
      }
    }

    // 添加GUI
    function addBtns() {
      // 增加 visited 样式,图片模式已阅的帖子变灰色 
      GM_addStyle(`.xw0 a:visited {color: grey;}`);
      if (window.location.href.match('/forum-') || window.location.href.match('/type-') || window.location.href.match('mod=forum')) { // 去掉高亮颜色标题
        document.querySelectorAll('[style="color: #2B65B7"]').forEach((e) => {
          e.style = '';
        })
      }

      // 生产消息盒子
      function genDiv() {
        let b = document.createElement('div'); //创建类型为div的DOM对象
        b.style.cssText = 'width: 220px;float: left;position: absolute;border-radius: 10px;left: auto;right: 5%;bottom: 20px;z-index:999';
        b.id = 'messageBox';
        return b; //返回修改好的DOM对象
      };
      document.querySelector('body').appendChild(genDiv()); // 消息盒子添加到body
      const messageBox = document.querySelector('#messageBox');
      const messageBoxBottom = parseInt(messageBox.style.bottom);
      window.onscroll = function () { //定位在右下角
        const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        messageBox.style.bottom = messageBoxBottom - scrollTop + 'px';
      }
      const status_loginned = document.querySelector('.status_loginned');
      const mnoutbox = document.querySelectorAll('.mnoutbox');

      // 在签到页面激活 定时签到
      if (location.href.match(`id=dsu_paulsign:sign`)) {
        let btn = genButton('定时签到', timeControl); //设置名称和绑定函数
        status_loginned.insertBefore(btn, mnoutbox[1]); //添加按钮到指定位置
        const video = genVideo();
        status_loginned.insertBefore(video, mnoutbox[1]); //添加视频到指定位置
      }


      if (window.location.href.match('/forum-')) {
        // 回帖输入框
        const input = genElement('textarea', 'inpreply', 1, 20);
        status_loginned.insertBefore(input, mnoutbox[1]); //添加文本域到指定位置
        // 感谢 按钮
        const thkBtn = genButton('添加本页', thankOnePage); //设置名称和绑定函数
        status_loginned.insertBefore(thkBtn, mnoutbox[1]); //添加按钮到指定位置
      } else if (location.href == `https://www.jkforum.net/forum.php`) { //在首页激活批量感谢功能
        // 回帖 按钮
        const repBtn = genButton('回帖', replyBtn); //设置名称和绑定函数
        status_loginned.insertBefore(repBtn, mnoutbox[1]); //添加按钮到指定位置
        // 感谢 按钮
        const thankBtn = genButton('感谢', thkBtn); //设置名称和绑定函数
        status_loginned.insertBefore(thankBtn, mnoutbox[1]); //添加按钮到指定位置  
        // 回帖输入框
        const input = genElement('textarea', 'inpreply', 1, 20);
        status_loginned.insertBefore(input, mnoutbox[1]); //添加文本域到指定位置  
        // 页码输入框
        const page = genElement2('input', 'inp_page');
        status_loginned.insertBefore(page, mnoutbox[1]); //添加输入框到指定位置
        // 批量感谢/回帖
        const btn = genButton('添加任务', thankBatch); //设置名称和绑定函数
        status_loginned.insertBefore(btn, mnoutbox[1]); //添加按钮到指定位置
      } else if (window.location.href.match('thread')) {
        // 回帖 按钮
        const repBtn = genButton('下载图片', downloadImgs); //设置名称和绑定函数
        status_loginned.insertBefore(repBtn, mnoutbox[1]); //添加按钮到指定位置

      }
    };

    function replyBtn() {
      if (!this.timer) {
        replyOrThk(this, 'reply');
      }
    }

    function thkBtn() {
      replyOrThk(this, 'thk');
    }

    // 定时签到
    function timeControl() {
      const _this = this; //获取对象
      clearInterval(_this.timer); //清除重复定时器
      document.querySelector('#video1').play(); // 播放视频,防止休眠
      if (!document.querySelector('#video1').paused) {
        const date = new Date()
        const holdTime = date.getTime();
        // 1000*60*60*24
        const hold = ((1000 * 60 * 60) - holdTime % (1000 * 60 * 60)); //通知持续时间,1小时-已运行分钟
        messageBox('防止休眠启动,请保持本页处于激活状态,请勿遮挡、最小化本窗口以及全屏运行其它应用!', hold);
        messageBox('定时签到中,请勿退出...', hold);
      } else {
        console.log(document.querySelector('#video1'));
      }
      const user = getUserFromName();
      const signtime = user.signtime; // 设定签到时间

      function control() {
        const nowtime = nowTime('seconds').split(' ')[1]; // 获取当前时间,到秒
        if (nowtime == signtime) {
          clearInterval(_this.timer);
          messageBox('执行中....');
          let retryTime = 0;
          for (let i = 0; i < user.signNum; i++) { //重试次数
            setTimeout(() => {
              sign(user);
              messageBox('执行第' + (i + 1) + '次');
              console.log('执行第' + (i + 1) + '次');
            }, retryTime += user.interTime) //重试间隔
          }
        } else {
          console.log('时间没有到:', signtime, '目前时间:', nowTime('seconds').split(' ')[1]);
        }
      }
      _this.timer = setInterval(control, 500);
    }

    async function sign(user) {
      var searchParams = new URLSearchParams();
      searchParams.append("formhash", user.formhash);
      searchParams.append("qdxq", user.mood);
      searchParams.append("qdmode", 1);
      searchParams.append("todaysay", user.todaysay);
      searchParams.append("fastreply", 1);
      const stringOrHtml = await postDataAs(user.signUrl, searchParams.toString()); // 直接post签到数据
      if (checkHtml(stringOrHtml)) { // 确认html
        const info = stringOrHtml.querySelector('.c').innerHTML.split('<')[0].trim(); // 解析html,返回字符串
        messageBox(info, 10000);
        console.log(info);
      } else {
        messageBox(stringOrHtml); //其它情况直接输出
      }
    }

    // 消息通知弹窗
    function messageBox(text, setTime = 5000) {
      function genBox(text, id) {
        let b = document.createElement('div');
        b.textContent = text; //修改内部文本为text
        b.style.cssText = 'width:100%;background-color:#64ce83;float:left;padding:5px 10px;margin-top:10px;border-radius:10px;color:#fff;    box-shadow: 0px 0px 1px 3px #ffffff;';
        b.id = id;
        return b; //返回修改好的DOM对象
      };
      const date = new Date(); // 生成时间 id 
      const timeId = 'a' + date.getTime();
      let textBox = genBox(text, timeId); // 初始化消息盒子
      let messageBox = document.querySelector('#messageBox');
      messageBox.appendChild(textBox); // 显示消息
      if (setTime && !isNaN(setTime)) { // 默认5秒删掉消息,可设置时间,none一直显示
        setTimeout(() => {
          messageBox.removeChild(document.getElementById(timeId));
        }, setTime);
      }
    }

    function chooceReply(user) {
      const inpreply = document.querySelector('#inpreply'); // 获取回复内容
      if (inpreply && inpreply.value) {
        user.replyMessage = []; // 中文分号分隔字符串
        inpreply.value.split(';').forEach((element) => {
          if (element) {
            user.replyMessage.push(element); // 中文分号分隔字符串
            user.userReplyMessage.push(element); // 存储自定义回帖内容,从头部添加
          }
        })
        GM_setValue(user.username, user); // 油猴脚本存储回帖内容
        // console.log("已使用自定义回复");
        messageBox("已使用自定义回复");
        return user.replyMessage.length;
      } else {
        if (user.fastReply.length > 1 && confirm("确认使用快速回复?否则使用历史回复")) { // 1 为错误信息
          GM_setValue(user.username, user); // 油猴脚本存储回帖内容
          // console.log("已使用快速回复");
          messageBox("已使用快速回复");
          return user.fastReply.length;
        } else if (user.userReplyMessage.length && confirm("确认使用历史自定义回复?")) {
          GM_setValue(user.username, user); // 油猴脚本存储回帖内容
          // console.log("已使用历史自定义回复");
          messageBox("已使用历史自定义回复");
          return user.userReplyMessage.length;
        } else {
          alert('没有获取到任何回复,请确认有浏览可快速回贴的版块的权限!否则需要手动输入回帖内容!');
          return -1;
        }
      }
    }

    async function thankOnePage() {
      const currentHref = window.location.href; // 获取当前页地址
      const fid = currentHref.split('-')[1]; // 获取板块fid
      if (document.querySelector('.showmenubox').querySelector('[class="chked"]')) { // 判断当前页是否处于图片模式
        if (confirm("是否切换到列表模式并刷新页面?")) { // 图片模式则切换为列表模式
          await getData('https://www.jkforum.net/forum.php?mod=forumdisplay&fid=' + fid + '&forumdefstyle=yes');
          location.reload();
        } else {
          messageBox('无法在图片模式运行!')
        }
      } else {
        messageBox('正在添加本页...');
        let replyLen = chooceReply(); //如果输入了值则使用用户值,如果没有则使用默认值;没有默认值则返回错误
        if (replyLen <= 0) {
          console.log('获取回帖内容失败!');
          messageBox('获取回帖内容失败!');
          return "获取回帖内容失败!";
        };
        getThreads(currentHref, fid, replyLen); // 获取当前页所有帖子地址
      }
    }

    async function thankBatch() {
      const page = document.querySelector('#inp_page').value;
      const reg = new RegExp(/^\d+-\d+-\d+$/);
      if (reg.test(page)) { //如果输入了正确地址则进行批量处理
        messageBox('正在添加:' + page);
        const user = getUserFromName();
        user.page = page;
        GM_setValue(user.username, user);
        let pageFrom = parseInt(page.split('-')[1]); // 获取起点页码
        const pageEnd = parseInt(page.split('-')[2]); // 获取终点页码
        const fid = page.split('-')[0]; // 获取版块代码

        await getData('https://www.jkforum.net/forum.php?mod=forumdisplay&fid=' + fid + '&forumdefstyle=yes'); //切换到列表模式,同步请求。
        messageBox('已切换到列表模式');

        let replyLen = chooceReply(user); //如果输入了值则使用用户值,如果没有则使用默认值;没有默认值则返回错误
        if (replyLen <= 0) {
          console.log('获取回帖内容失败!');
          messageBox('获取回帖内容失败!');
          return "获取回帖内容失败!";
        };

        function sendPage() {
          let currentHrefPage = 'https://www.jkforum.net/forum-' + fid + '-' + pageFrom + '.html'; //生成帖子列表地址
          getThreads(currentHrefPage, fid, replyLen);
          console.log('当前地址:', currentHrefPage, '页码:', pageFrom);
          pageFrom++;
        };
        while (pageFrom <= pageEnd) {
          sendPage();
        }
      } else {
        messageBox('请输入回帖列表页码,格式:版块代码-起点页-终点页 ;例如:640-1-2 ;版块代码见版块URL中间数字:forum-640-1', 10000);
      }
    }
    // 获取当前页所有帖子地址
    function getThreads(currentHref, fid, replyLen) {
      const httpRequest = new XMLHttpRequest();
      httpRequest.open('GET', currentHref, true);
      httpRequest.send();
      httpRequest.onload = () => {
        if (httpRequest.readyState == 4 && httpRequest.status == 200) {
          const data = httpRequest.responseText;
          // 数据类型转换
          let htmlData = document.createElement('div');
          htmlData.innerHTML = data;

          const user = getUserFromName(); //获取user对象,必须在用户输入值后面,不然取不到快速回复
          //帖子类名 40个a标签数组 
          let hrefs = htmlData.querySelectorAll('.s');
          // 获取作者昵称和 UID
          let cites = htmlData.querySelectorAll('cite a');

          // 以 fid 创建对象,如果fid存在则写入fid的数组的fidthreads属性的数组内;否则创建新的 fidthreads,自我调用
          const fidthreads = {
            fid: fid,
            fidTime: 0,
            fidRepIndex: 0, // 记录此版块上次回复的位置,用于解决无法遍历到后续增加的帖子;
            fidThkIndex: 0, // 记录此版块上次感谢的位置,用于解决无法遍历到后续增加的帖子;
            fidthreads: [],
          }

          let fidTime = 0; // 统计总时间
          function newFid() {
            if (user.replyThreads.length) {
              for (let i = 0; i < user.replyThreads.length; i++) {
                if (user.replyThreads[i].fid == fid) {
                  // console.log(fid);
                  const info = addThrInfo(user.replyThreads[i]);
                  user.replyThreads[i].fidTime += fidTime; // 累加时间
                  GM_setValue(user.username, user);
                  return info; // 匹配到则退出循环 // 传入对应对象
                }
              }
              // 如果没匹配到同样增加
              user.replyThreads.push(fidthreads);
              newFid();
            } else {
              user.replyThreads.push(fidthreads); // 初始化threads
              newFid();
            }
          }
          // 回帖变量随即范围限制
          let start = 0;
          if (replyLen == user.fastReply.length || replyLen == user.userReplyMessage.length) { // 判断起始位置
          } else {
            start = user.userReplyMessage.length - replyLen; // 用户数组长-增加的数据长=起始位置;
            replyLen = user.userReplyMessage.length; // 结束位置
            // console.log(start, replyLen - 1, "变量范围");
          }

          function addThrInfo(elem) {
            // 遍历去除回帖用户
            for (let i = 0; i < cites.length; i += 2) {
              // 加入数组
              const touser = cites[i].innerHTML;
              const touseruid = cites[i].href.split('uid=')[1]; // href="home.php?mod=space&uid=1123445"
              const href = hrefs[i / 2].href;
              // 获取帖子ID
              const tid = href.split('-')[1];
              // 确保帖子的唯一性
              for (let i = 0; i < elem.fidthreads.length; i++) {
                const element = elem.fidthreads[i];
                if (element.tid == tid) {
                  return `${currentHref} 中: thread-${tid}-1-1 :此帖子已在任务列表,已跳过此页!`;
                }
              }
              // 感谢数据
              var thkData = new URLSearchParams();
              thkData.append("formhash", user.formhash);
              thkData.append("tid", tid);
              thkData.append("touser", touser);
              thkData.append("touseruid", touseruid);
              thkData.append("handlekey", "k_thankauthor");
              thkData.append("addsubmit", "true");
              const replyIndex = rdNum(start, replyLen - 1); // 从返回的输入长度获取随机值
              const randomTime = rdNum(user.interval, user.differ + user.interval);
              const thread = {
                tid: tid,
                touseruid: touseruid,
                touser: touser,
                thkData: thkData.toString(),
                replyIndex: replyIndex, // 回帖随机数
                replyLen: replyLen, // 用于判断使用的哪个数组,和确定起始位置
                randomTime: randomTime, // 回帖时间随机数
              }
              fidTime += randomTime;
              elem.fidthreads.push(thread); // 给对象数组添加
            }
            GM_setValue(user.username, user);
            // console.log(user.replyThreads.length)
            messageBox(`${currentHref} 回帖列表任务添加成功!`)
            console.log(`${currentHref} 回帖列表任务添加成功!`)
          }
          // 错误提示
          const info = newFid();
          if (info) {
            messageBox(info);
            console.log(info);
          }
        };
      };
    };

    async function replyOrThk(_this, type = 'reply') { // 回帖函数
      const video = genVideo(); //需要视频时再加载视频,提高性能
      document.querySelector('body').appendChild(video); //添加视频到指定位置
      document.querySelector('#video1').play(); // 播放视频,防止休眠
      if (!document.querySelector('#video1').paused) {
        messageBox('防止休眠启动,请保持本页处于激活状态,请勿遮挡、最小化本窗口以及全屏运行其它应用!', 'none');
      } else {
        console.log(document.querySelector('#video1'));
      }
      const user = getUserFromName();
      const thkUrl = user.thkUrl;
      let fidIndex = 0; // 当前回帖版块序号
      let thkFidIndex = 0; // 当前感谢版块序号
      if (!user.replyThreads.length) {
        messageBox('任务列表为空,请先添加任务!');
        console.log('任务列表为空,请先添加任务!');
        return 0; // 打断了通知消息的异步执行
      } else if (type == 'reply') {
        console.log(type, ":开始回帖...");
      } else {
        console.log(type, ":开始感谢...");
      }
      cricleReplyForum();

      function cricleReplyForum() {
        if ((type == 'reply' && fidIndex < user.replyThreads.length) || (type == 'thk' && thkFidIndex < user.replyThreads.length)) { // 分别处理感谢和回帖
          const elementForum = user.replyThreads[(type == 'reply') ? fidIndex : thkFidIndex]
          const fid = elementForum.fid;
          let fidRepIndex = elementForum.fidRepIndex; // 上次回复位置
          let fidThkIndex = elementForum.fidThkIndex; // 上次感谢位置
          if (type == 'reply') {
            console.log(fid + "-版块,当前位置:" + fidRepIndex + " ,总数:" + elementForum.fidthreads.length + ",版块总需" + (elementForum.fidTime / 1000 / 60).toFixed(1) + " 分钟时间");
            messageBox(fid + "-版块,当前位置:" + fidRepIndex + " ,总数:" + elementForum.fidthreads.length + ",版块总需" + (elementForum.fidTime / 1000 / 60).toFixed(1) + " 分钟时间", elementForum.fidTime);
          } else if (type == 'thk') {
            console.log(fid + "-版块,当前位置:" + fidThkIndex + " ,总数:" + elementForum.fidthreads.length);
            messageBox(fid + "-版块,当前位置:" + fidThkIndex + " ,总数:" + elementForum.fidthreads.length);
          }
          cricleReply();

          async function cricleReply() {
            if ((elementForum.fidthreads.length > fidRepIndex && type == 'reply') || (elementForum.fidthreads.length > fidThkIndex && type == 'thk')) { // 分别处理感谢和回帖
              switch (type) {
                case 'reply': {
                  const elementThr = elementForum.fidthreads[fidRepIndex];
                  const tid = elementThr.tid;
                  const replyIndex = elementThr.replyIndex;
                  const replyLen = elementThr.replyLen;
                  const randomTime = elementThr.randomTime;
                  const replyUrlData = new URLSearchParams();
                  replyUrlData.append("fid", fid);
                  replyUrlData.append("tid", tid);
                  replyUrlData.append("extra", "page%3D1");
                  replyUrlData.append("replysubmit", "yes");
                  replyUrlData.append("infloat", "yes");
                  replyUrlData.append("inflohandlekeyat", "fastpost");
                  replyUrlData.append("inajax", "1");
                  const date = new Date();
                  const posttime = parseInt(date.getTime() / 1000); // 生产时间戳
                  // 拼接回帖报文
                  const replyData = new URLSearchParams();
                  if (replyLen == user.fastReply.length) {
                    replyData.append("message", user.fastReply[replyIndex]);
                  } else {
                    replyData.append("message", user.userReplyMessage[replyIndex]);
                  }
                  replyData.append("posttime", posttime);
                  replyData.append("formhash", user.formhash);
                  replyData.append("usesig", 1);
                  replyData.append("subject", "++");


                  const data = await postDataAs(user.replyUrl + replyUrlData, replyData.toString());
                  if (checkHtml(data)) { // 确认html
                    const info = data.querySelector('script').innerHTML.split(`, `)[1];
                    messageBox(info.split(',')[0].slice(1) + ',' + info.split(',')[1] + '!'); // 返回html成功消息
                  } else {
                    messageBox(data, 'none'); //其它情况直接输出
                    console.log(data);
                  }
                  console.log("序号:" + fidRepIndex, '随机号:', replyIndex, '用时:', randomTime, "帖子:", tid, '内容:', replyData.get("message")); //测试使用  
                  elementForum.fidRepIndex = ++fidRepIndex;
                  GM_setValue(user.username, user);
                  clearInterval(_this.timer);
                  _this.timer = setInterval(async () => {
                    clearInterval(_this.timer);
                    cricleReply();
                  }, randomTime);
                  break;
                }
                case 'thk': {
                  const elementThr = elementForum.fidthreads[fidThkIndex];
                  const thkData = elementThr.thkData;
                  const data = await postDataAs(thkUrl, thkData); //post感谢数据
                  if (checkHtml(data)) {
                    const info = data.querySelector('.alert_info').innerHTML.split('<')[0].trim(); //去除html,返回字符串
                    messageBox(info);
                    console.log(info);
                  } else {
                    console.log(data);
                    messageBox(data); //其它情况直接输出
                  }
                  console.log(fidThkIndex, thkData); //post感谢数据
                  elementForum.fidThkIndex = ++fidThkIndex;
                  GM_setValue(user.username, user);
                  clearInterval(_this.timer);
                  _this.timer = setInterval(async () => {
                    clearInterval(_this.timer);
                    cricleReply();
                  }, 1000) // 感谢防刷
                  break;
                }

                default:
                  console.log("参数不在范围");
                  break;
              }
            } else {
              if (type == 'thk') {
                thkFidIndex++; // 翻页
              } else if (type == 'reply') {
                fidIndex++; // 翻页
                messageBox(fid + ":版块回帖完成!");
              }
              GM_setValue(user.username, user);
              cricleReplyForum();
            }
          }
        } else {
          if (type == 'thk') {
            messageBox("全部感谢完成!");
            console.log("全部感谢完成!");
          } else if (type == 'reply') {
            messageBox("全部回帖完成!");
            console.log("全部回帖完成!");
            GM_notification("全部回帖完成!");
          }
        }
      }
      GM_setValue(user.username, user);
    };

    // n, m 范围随机整数生成 
    function rdNum(n, m) {
      let c = m - n + 1;
      return Math.floor(Math.random() * c + n);
    }

    // GM_xmlhttpRequest GET异步通用模块
    function getData(url, type = "document", usermethod = "GET") {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: usermethod,
          url: url,
          responseType: type,
          onload: function (response) {
            if (!response) {
              try {} catch (err) {
                console.log(err);
              }
            }
            resolve(response.response);
          },
          onerror: function (error) {
            console.log("网络错误");
            messageBox("网络错误");
            reject(error);
          }
        });
      });
    }
    // GM_xmlhttpRequest POST异步通用模块
    function postDataAs(url, postData, type = "document", usermethod = "POST") {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: usermethod,
          url: url,
          headers: {
            "Content-Type": "application/x-www-form-urlencoded"
          },
          data: postData,
          responseType: type,
          onload: function (response) {
            if (!response) {
              try {} catch (err) {
                console.log(err);
              }
            }
            resolve(turnCdata(response.response));
          },
          onerror: function (error) {
            console.log("网络错误");
            messageBox("网络错误");
            reject(error);
          }
        });
      });
    }

    // POST返回 xml数据类型转换成 字符串或html 模块
    function turnCdata(xmlRepo) {
      let data = xmlRepo.getElementsByTagName("root")[0].childNodes[0].nodeValue;
      if (replaceHtml(data)) { // 如果判断去掉html是否还有文字,否则返回html
        return replaceHtml(data); // 去掉html内容,返回文字
      } else {
        let htmlData = document.createElement('div'); // 数据类型转换成 html
        htmlData.innerHTML = data;
        return htmlData;
      }
    }

    // 编码统一资源定位符模块
    function turnUrl(data, type) {
      if (type) {
        return decodeURI(data);
      } else {
        return encodeURI(data);
      }
    }
    // 判断html和字符串是不是html
    function checkHtml(htmlStr) {
      if (htmlStr.nodeName) {
        return true;
      } else {
        let reg = /<[^>]+>/g;
        return reg.test(htmlStr);
      }
    }
    // 过滤html标签、前后空格、特殊符号
    function replaceHtml(txt) {
      const reg3 = /[\a|\r|\n|\b|\f|\t|\v]+/g; //去掉特殊符号
      const reg = /<.+>/g; //去掉所有<>内内容
      // 先reg3,\n特殊符号会影响reg的匹配
      return txt.replace(reg3, '').replace(reg, '').trim();
    }

    function nowTime(time) {
      const date = new Date();
      const year = date.getFullYear();
      const month = date.getMonth() + 1;
      const day = date.getDate();
      let hours = date.getHours();
      let minutes = date.getMinutes();
      let seconds = date.getSeconds();
      // 补零
      if (hours < 10) {
        hours = `0${hours}`;
      }
      if (minutes < 10) {
        minutes = `0${minutes}`;
      }
      if (seconds < 10) {
        seconds = `0${seconds}`;
      }
      switch (time) {
        case 'year': {
          return year;
        }
        case 'month': {
          return `${year}/${month}`;
        }
        case 'day': {
          return `${year}/${month}/${day}`;
        }
        case 'hours': {
          return `${year}/${month}/${day} ${hours}`;
        }
        case 'minutes': {
          return `${year}/${month}/${day} ${hours}:${minutes}`;
        }
        case 'seconds': {
          return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
        }
        default:
          return "输入时间";
      }
    }

    // 比较键
    function compaObjKey(source, target) {
      let count = 0;
      Object.keys(source).forEach(ea => {
        Object.keys(target).forEach(eb => {
          if (ea === eb) {
            count++;
          }
        })
      });
      if (count == Object.keys(source).length) {
        return true;
      } else {
        return false;
      }
    }
    // 赋值对象的值
    function copyObjVal(target, source) {
      Object.keys(source).forEach((key) => {
        target[key] = source[key];
      });
      return target;
    }

    function genButton(text, foo, id) {
      let b = document.createElement('button'); //创建类型为button的DOM对象
      b.textContent = text; //修改内部文本为text
      b.style.cssText = 'margin:16px 10px 0px 0px;float:left' //添加样式(margin可以让元素间隔开一定距离)
      b.addEventListener('click', foo); //绑定click的事件的监听器
      if (id) {
        b.id = id;
      } //如果传入了id,就修改DOM对象的id
      return b; //返回修改好的DOM对象
    }

    function genElement(type, id, val1, val2) {
      let b = document.createElement(type); //创建类型为button的DOM对象
      b.style.cssText = 'margin:16px 10px 0px 0px;float:left' //添加样式(margin可以让元素间隔开一定距离)
      b.rows = val1;
      b.cols = val2;
      // 油猴脚本存储回帖内容
      b.placeholder = '中文分号;分隔回帖内容';
      if (id) {
        b.id = id;
      } //如果传入了id,就修改DOM对象的id
      return b; //返回修改好的DOM对象
    }

    function genElement2(type, id) {
      let b = document.createElement(type); //创建类型为button的DOM对象
      b.style.cssText = 'margin:16px 10px 0px 0px;float:left;width:80px' //添加样式(margin可以让元素间隔开一定距离)
      if (id) {
        b.id = id;
      }
      const user = getUserFromName();
      if (user && user.page) {
        b.value = user.page;
      }
      b.placeholder = `版块-1-2`;
      return b; //返回修改好的DOM对象
    }

    function genVideo() {
      let video = document.createElement('video');
      video.style.cssText = 'display: none; z-index: -1000;width:0;height:0;'
      video.id = 'video1';
      video.loop = 'true';
      video.autoplay = 'true';
      let source = document.createElement('source');
      source.src = 'https://raw.githubusercontent.com/Eished/jkforum_helper/main/video/light.mp4';
      source.type = "video/mp4"
      video.append(source);
      return video;
    }

    addBtns(); // 添加DOM
    creatUser(); // 添加用户
  }
})();