Node写一个掘金自动签到脚本

需求分析

  • 每天规定时间自动签到
  • 签到后能进行免费自动抽奖
  • 脚本执行成功后, 能发送签到结果以及抽奖结果到邮箱
  • 可以支持多个人并发运行脚本

设计思路

  1. 首先每个用户应该是独立执行的脚本,通过pm2来管理脚本进程
  2. 用户可以手动设置掘金Cookie以及脚本执行时间
  3. 用户可以设置邮箱来接收签到结果
  4. 用户还可以查看脚本执行情况(需要一个日志列表)
  5. 用户如果不想自动签到还可以手动关闭签到脚本

准备工作

环境搭建

  • 安装nvm管理 node
  • 安装node-schedule来实现定时任务执行
  • 安装nodemailer来发送邮件通知
  • 安装axios来请求掘金API
  • 安装pm2来管理多个人开启的进程

前期准备

  • 需要授权得到授权码的邮件地址用于向用户发送邮件通知
  • 需要一个服务器来执行应用, 如果只自己使用则只需要本地启动, 保持电脑不关机

掘金相关API

项目开始

搭建一个登录页面

  1. 登录即注册,如果是新的用户名,则是注册;如果是存在的则会校验用户密码
  2. 邮箱可填可不填,主要用于是否需要邮件通知
javascript 复制代码
const { username, password } = loginForm
if (!username || !password) {
    resolve(returnMsg('账号名或密码不可为空', null, 500))
    return
}
if (!/^[A-Za-z0-9]+$/.test(username)) {
    resolve(returnMsg('账号名只能包含英文与数字', null, 500))
    return
}
const userInfo = checkUserIsExist(username)
if (userInfo && userInfo.password !== password) {
    resolve(returnMsg('密码不正确', null, 500))
    return
}
const fileName = `${__dirname}/config/token.js`
const { info: fileContent, watcherToken } = handleLoginInfo(userInfo, loginForm)
await writeFileSync(fileName, fileContent)
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Set-Cookie', [`username=${username}`, `watcherToken=${watcherToken}`])
// 用户名已存在时登陆 => 会重新生成watcherToken
resolve(returnMsg())

主要代码接口

  1. checkUserIsExist: 主要从用户表查看是否存在当前用户
  2. handleLoginIofo: 主要是生成一个用户的唯一watcherToken
  3. writeFileSync(): 主要是将用户写入用户表,新用户直接写入,老用户主要是更新watcherToken以及邮箱地址
  4. 最后设置Cookie

进入主页面

主页面主要分为: 配置模块,脚本模块,日志模块,活动列表

配置模块

点击设置进入配置页面

  1. 可以更新掘金Cookie,设置的Cookie会存入用户表的对应用户下
  2. 可以设置接收签到结果邮箱接收地址
  3. 可以设置脚本执行时间(不设置默认签到时间:早上8点整)
  4. 所有的设置需要重启脚本才能起效
脚本模块
  1. 可以开启自动签到脚本
  2. 可以关闭自动签到脚本
  3. 下面脚本进程就是检测当前脚本是否开启成功,对应name就是 autoScript-<登录名>
日志模块
  1. 获得所有脚本签到结果日志列表
  2. 包括脚本签到执行日期,剩余矿石数,免费抽奖结果,邮件发送是否成功等
  3. 可以手动去更新日志列表(不过每日签到应该作用不大)
活动列表
  1. 不需要设置掘金Token也可以获取的数据
  2. 主要展示掘金相关的活动
  3. 活动主要包括当前进行中的以及历史的一些活动

核心代码

自动签到脚本执行

  1. 开启一个定时任务执行自动签到主函数
javascript 复制代码
const rule = new schedule.RecurrenceRule()
rule.hour = hour
rule.minute = minute
rule.second = second
schedule.scheduleJob(rule, main)
  1. 主函数流程
graph LR A[获得当前用户对应的掘金Cookie] --> B[查看是否已经签到] -- 已签到 --> C[则无需再去签到] B -- 未签到 --> 则调用接口去签到 --> D[获取签到结果] --> E[获取当前剩余矿石数量] --> F[获取免费抽奖次数] C --> D
graph LR A[获取免费抽奖次数] -- 次数0 --> B[没有免费次数则无需调接口去抽奖, 不然浪费矿石] B --> D A -- 次数1 --> C[则调用接口去抽奖] --> D[获取抽奖结果] --> E[根据当前用户是否设置邮件来发送执行结果]
javascript 复制代码
async function main () {
    LOG_MSG.currentTime = `${getCurrentDate()} ${getCurrentTime()}`
    if (!username || !COOKIE) {
        LOG_MSG.error = '用户不存在,脚本执行失败'
        await toLog()
        return
    }
    const isSignedObj = await checkIsSignedIn(COOKIE)
    if (isSignedObj) {
        const { err_no, err_msg } = isSignedObj
        if (err_no ===  403 || err_msg === 'must login') {
            LOG_MSG.error = '当前Token登录失败, 请重新设置正确的Token'
            toLog()
            // Todo: 关闭脚本?
            return
        } else {
            LOG_MSG.signStatus = true
            EMAIL_MSG.signStatus = '今日已签到'
        }
    } else {
        const signStatusObj = await toSignIn(COOKIE)
        if (signStatusObj.data) {
            LOG_MSG.signResult = 'true'
            EMAIL_MSG.signStatus = '签到成功'
        } else {
            LOG_MSG.signResult = signStatusObj.err_msg || 'false'
            EMAIL_MSG.signStatus = signStatusObj.err_msg || '签到失败'
        }
    }
    const count = await toGetPointCount(COOKIE)
    LOG_MSG.remainedPoint = count || 0
    EMAIL_MSG.pointCount = count || 0
    const times = await queryFreeTimes(COOKIE)
    if (times) {
        LOG_MSG.freeDrawTimes = times
        const { lottery_name } = await toDraw(COOKIE)
        LOG_MSG.drawResult = lottery_name
        EMAIL_MSG.drawStatus = `${lottery_name}\n`
    } else {
        LOG_MSG.freeDrawTimes = 0
        EMAIL_MSG.drawStatus = '今日免费抽奖次数已用完'
    }
    if (email) {
        try {
            const emailResult = await sendEmail(email, handleEmailMessage())
            LOG_MSG.emailStatus = emailResult.messageId ? true : false
        } catch {
            LOG_MSG.emailStatus = false
        }
    }
    toLog()
}
  1. 邮件发送
javascript 复制代码
const transporter = nodemailer.createTransport({
    host: SENDER.HOST,
    port: SENDER.PORT,
    secureConnection: true, // 不写这句会报错:Greeting never received
    auth: {
        user: SENDER.USER, 
        pass: SENDER.PASS  
    }
})

export const sendEmail = async (userEmail, content = RECIPIENT.DEFAULTMSG) => {
    return await transporter.sendMail({
        from: SENDER.USER,          // 发送邮件的地址
        to: userEmail,                   // 接收邮件的地址
        subject: RECIPIENT.SUBJECT, // 邮件标题
        text: content,              // 有 html,优先 html
        html: '',                   // html body
        attachments: ''             // 附件
    })
}

SENDER.USER: 设置一个用于发送给用户的邮箱

SENDER.PASS: 该邮箱对应的授权码

javascript 复制代码
export const SENDER = {
    HOST: 'smtp.qq.com',
    PORT: 465,
    USER: '',  // 授权smtp邮箱地址
    PASS: ''   // 申请的授权码
}

export const RECIPIENT = {
    // 由用户设置
    // USER: '',
    SUBJECT: '掘金签到成功',
    DEFAULTMSG: `您于${new Date().toLocaleString()}, 在自动签到系统中签到成功`
}

Github代码地址
测试Demo地址

PS: 这是作者自己搭的服务, 读者可自动决定是否加入开启敲到脚本
PS: 如果有任务问题, 请在下面评论区提问

相关推荐
xiaofeichaichai4 小时前
Webpack
前端·webpack·node.js
Python私教7 小时前
把开源 Agent 打包成"解压双击即用"的 Windows 便携包:一条命令的完整实现
node.js
没事别瞎琢磨9 小时前
十一、审计与 Run Session——每一步操作都被记录
人工智能·node.js
没事别瞎琢磨9 小时前
十六、AgentSandbox——把所有模块串起来的编排类
人工智能·node.js
没事别瞎琢磨9 小时前
十二、网络代理与白名单规则引擎
人工智能·node.js
没事别瞎琢磨9 小时前
十四、Git Worktree 隔离执行
人工智能·node.js
没事别瞎琢磨11 小时前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨11 小时前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨12 小时前
六、输出捕获与截断
人工智能·node.js
没事别瞎琢磨12 小时前
七、敏感路径预检——Protected Paths
人工智能·node.js