掘金自动签到 ➕ 定时执行 ➕邮箱推送 你还想要啥❓
前言冒着被关进小黑屋的风险 写了个掘金自动签到抽奖程序 包含了自动执行、签到、免费抽奖、沾喜气、邮件通知的脚本,以后再也不用每天忘记签到了😄
事情是这样的 元旦那天由于玩的太嗨 忘记了掘金签到这么一回事 作为一名专业的摸鱼🐟选手 怎么能断签呢(好吧 我是为了第二天5120矿石)果断买了补签卡 后来在想 为啥不写一个每天自动执行的签到脚本呢??
重要❗
掘金团队已经对签到脚本采取措施,禁止🙅♂️使用自动签到脚本,违者将清空所有矿石或者封号,具体规则可参见禁止脚本签到行为沸点 ,大家还是遵循社区规范,每天登陆掘金进行签到。之前有使用过该项目的,建议将fork下来的仓库workflow手动禁止,或者直接删除项目,文章中的github项目链接我已删除。
具备能力[x] action
每天9点定时执行 [x] 邮件通知 [x] 签到 [x] 沾喜气 [x] 抽奖 [ ] 设定想要兑换的周边 自动计算还需要签到多少天 目前就具备这么多能力 项目会持续维护 添加的新的功能(前提是不会被优弧关起来)如果有更好的项目可以在评论区提出 Start quickly用编辑器打开项目后 需要将带有手动填写 的几项数据修改为自己的 其他不要动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 module .exports = { cookie : '' , baseUrl : 'https://api.juejin.cn' , api : { ... }, emailConfig : { service : '163' , email : '' , pass : '' , } }
cookie获取方式cookie 有过期时间 大概是一个月 或者是退出登陆也会过期 登陆进入到掘金,F12 打开控制台,选择network 后随便点击一个接口,找到请求头中的cookie
,选中数据后右键复制值
邮箱设置这里以163邮箱为例 qq邮箱同理 如果是163邮箱 直接将service
字段设置为163(qq邮箱就写qq) 然后填入你自己的邮箱 邮件发送成功 登陆邮箱会看到你给自己发了一条邮件 就像这样
授权码获取⚠️: 登陆进入163邮箱 打开设置
将以下几个设置打开 打开IMAP/SMTP
服务时会弹窗发送短信 微信扫码后就可以发送短信(qq邮箱这一步开启需要手动进行验证发送短信)
我这里已经添加过了 就直接点击新增授权 也是一样会弹出二维码扫码发送短信
短信发送完毕后点击我已发送 然后就会得到你的授权码(注意授权码只展示一次) 将授权吗粘贴到配置文件中的 pass
字段
将所有的参数填入无误后 可以用命令node index.js
本地运行 可以收到邮件并且邮件里有日志消息 恭喜你🎉 以后再也不用每天签到了(会不会被官方打死)
确认无误后将修改后的项目push
项目已经设置了自动执行 每天9点 会自动执行签到 并且发送邮件进行通知
自从用了自动签到后 妈妈再也不用担心我忘记签到了 兑换Switch不是梦
具体实现如果只关注脚本功能 看到这里就可以左拐🚪了 如果对实现感兴趣 这里也和你分享一下具体的实现思路
有了想法之后就开始去扒掘金签到相关的接口 挨个接口点开看 都是给了些啥数据 每个数据都是用来干啥的 经过漫长的调试后 脚本签到能力就完成了 功能主要由一下几个接口组成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 checkIn : '/growth_api/v1/check_in' ,getCheckStatus : '/growth_api/v1/get_today_status' ,getCheckInDays : '/growth_api/v1/get_counts' ,getCurrentPoint : '/growth_api/v1/get_cur_point' ,getlotteryStatus : '/growth_api/v1/lottery_config/get' ,draw : '/growth_api/v1/lottery/draw' ,dipLucky : '/growth_api/v1/lottery_lucky/dip_lucky' getLuckyUserList : '/growth_api/v1/lottery_history/global_big'
接下来就很简单了 接接口嘛 谁还不会了 找个请求库直接干 这里我选用的是axios
先配置一下请求 在index.js
写入 将config
文件中的cookie
丢进请求头中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 axios.defaults.baseURL = config.baseUrl axios.defaults.headers['cookie' ] = config.cookie axios.interceptors.response.use((response) => { const { data } = response if (data .err_msg === 'success' && data .err_no === 0 ) { return data } else { return Promise.reject(data .err_msg) } }, (error) => { return Promise.reject(error) })
接下来就直接请求接口,请求循序依次为
以下主要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 const getCheckStatus = async ( ) => { try { const getCheckStatusRes = await axios ({ url : config.api .getCheckStatus , method : 'get' }) return getCheckStatusRes.data } catch (error) { throw `查询签到失败!【${error} 】` } } const getCurrentPoint = async ( ) => { try { const getCurrentPointRes = await axios ({ url : config.api .getCurrentPoint , method : 'get' }) console .log (`当前总矿石: ${getCurrentPointRes.data} 数` ) } catch (error) { throw `查询矿石失败!${error.err_msg} ` } } const getlotteryStatus = async ( ) => { try { const getlotteryStatusRes = await axios ({ url : config.api .getlotteryStatus , method : 'get' }) return getlotteryStatusRes.data .free_count === 0 } catch (error) { throw `查询免费抽奖失败!【${error} 】` } } const getLuckyUserHistoryId = async ( ) => { try { const luckyList = await axios ({ url : config.api .getLuckyUserList , method : 'post' }) return luckyList.data .lotteries [Math .floor (Math .random () * luckyList.data .lotteries .length )]?.history_id } catch (error) { throw `获取沾喜气列表用户historyId失败` } } const dipLucky = async ( ) => { try { const historyId = await getLuckyUserHistoryId () const dipLuckyRes = await axios ({ url : config.api .dipLucky , method : 'post' , data : { lottery_history_id : historyId } }) console .log (`占喜气成功! 🎉 【当前幸运值:${dipLuckyRes.data.total_value} /6000】` ) } catch (error) { throw `占喜气失败! ${error} ` } } const draw = async ( ) => { try { const freeCount = await getlotteryStatus () if (freeCount) { throw '今日免费抽奖以用完' } const drawRes = await axios ({ url : config.api .draw , method : 'post' }) console .log (`恭喜你抽到【${drawRes.data.lottery_name} 】🎉` ) await dipLucky () if (drawRes.data .lottery_type === 1 ) { await getCurrentPoint () } } catch (error) { console .error (`抽奖失败!=======> 【${error} 】` ) } } const getCheckInDays = async ( ) => { try { const getCheckInDays = await axios ({ url : config.api .getCheckInDays , method : 'get' }) return { continuousDay : getCheckInDays.data .cont_count , sumCount : getCheckInDays.data .sum_count } } catch (error) { throw `查询签到天数失败!🙁【${getCheckInDays.err_msg} 】` } } const checkIn = async ( ) => { try { const checkStatusRes = await getCheckStatus () if (!checkStatusRes) { const checkInRes = await axios ({ url : config.api .checkIn , method : 'post' }) console .log (`签到成功,当前总矿石${checkInRes.data.sum_point} ` ) const getCheckInDaysRes = await getCheckInDays () console .log (`连续抽奖${getCheckInDaysRes.continuousDay} 天 总签到天数${getCheckInDaysRes.sumCount} ` ) await draw () } else { console .log ('今日已经签到 ✅' ) } } catch (error) { console .error (`签到失败!=======> ${error} ` ) } }
自动执行关于自动执行 我最开始想的方案是通过服务器部署 开启一个定时任务去执行 这种方式需要有服务器 比较麻烦 也有人用云函数 我又懒得去注册 后了找到一种方案就是 白嫖Github Action
通过CI
设置定时任务 每天自动执行 Github
人人都有 要求也较低 由于我不是很懂CI
这方面的东西 这次为了脚本也只是学了个皮毛 具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 name: jjCheckInScript on: schedule: - cron: "0 1 * * *" jobs: start: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 [[安装node ]].js - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '14' - name: npm install run: npm install - name: Start task run: node index.js
这里关键的代码是schedule
将触发任务的方式改为定时执行 到了设定好的时间后会自动执行任务 任务会以最新版本的ubuntu
系统进行运行 安装node
安装项目中的依赖后 执行index.js
中的代码 如果想要修改执行时间 按照minute hour day month week
的格式修改schedule
字段即可(设置的时间是UTC
北京时间 需要**+8**)
邮件发送📧邮件发送这里选用的是Nodemail 库 它的功能十分强大 支持多种邮箱服务 支持HTML
内容、纯文本内容、附件、图片等等 发送邮件方式也很简单 具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 const nodemailer = require ('nodemailer' ) const logs = []console .oldLog = console .log console .oldErr = console .error console .log = (str ) => { logs.push ({ type : 'success' , text : str }) console .oldLog (str) } console .error = (str ) => { logs.push ({ type : 'error' , text : str }) console .oldErr (str) } const sendEmail = async ( ) => { try { const template = ejs.compile (fs.readFileSync (path.resolve (__dirname, 'email.ejs' ), 'utf8' )); const transporter = nodemailer.createTransport ({ service : process.env .SERVICE , port : 465 , secure : true , secureConnection : true , auth : { user : process.env .EMAIL , pass : process.env .PASS , } }) await transporter.sendMail ({ from : process.env .EMAIL , to : process.env .EMAIL , subject : '掘金签到通知🔔' , html : template ({ logs : logs }) }) } catch (error) { console .error (`邮件发送失败!${error} ` ) } }
邮件内容 我这里用ejs
写了一个简单的日志模版 用来承载脚本的log
在前面我对console.log console.error
进行重写 将str
存储到logs
数组中 吧logs
数据传入模版引擎生成html
邮件发送其实很简单 具体配置可以查看官方文档 这里就解释一下用到的配置
service: 邮箱服务 Node mai l
内部已经支持了很多邮箱服务 如果填写这个字段就不需要写host
host: 邮箱的主机IP地址 这一项一版在开启IMAP/SMTP
后会展示邮箱的IP地址prot: 端口号 默认的为465
secure: 配置安全链接secureConnection: 使用SSL
(默认为false)auth.user: 发送者邮箱auth.pass: 邮箱授权码from: 发送者邮箱to: 接受者邮箱subject: 邮件主题html: 邮件内容html
字符串 Actions secrets 密码安全关于cookie
邮箱授权码 刚开始是写在一个配置文件夹里面 后来啦哥提出了更好的方式 就是用Actions secrets
这种方式可以避免关键数据暴露 (万一那个无聊的家伙拿你cookie去梭哈了呢😏) 还是小心为上
其实使用也很简单 我之前对这块完全不熟悉 看了会官方文档 三下五除二就弄好了 在添加好secrets
数据后 我们需要在blank.yml
文件中对数据进行获取 获取方式也很简单 直接通过${{secrets.youKey}}
就可以获取到 以该项目为例
1 2 3 4 5 6 # 环境变量 env : COOKIE : ${{ secrets.COOKIE }} PASS : ${{ secrets.PASS }} EMAIL : ${{ secrets.EMAIL }} SERVICE : ${{ secrets.SERVICE }}
在设置完之后 我们就可以在环境变量中使用这些数据 就像这样
1 2 process.env .COOKIE process.env .EMAIL
但是获取到的数据在Actions
中是无法进行展示的 在输出日志中 你定义的所有密码都会被清除 并在输出日志之前用星号替换 也是为了防止泄漏
如果觉得这样还不够安全 在代码中可以随意使用到数据 可以尝试一下对密码进行加密
Q&A 自动执行延迟在开发测试的时 发现jobs没有按时执行 九点五分到公司打开actions
时发现并没有执行jobs
刚开始还以为是cron
时间填写错误 修改时间后发现github actions
定时任务会有延迟 延迟时间几分钟到十几分钟甚至一小时都有 但这个并不影响我们签到功能 只要是在今天签到都可以
以我测试为例 将 corn
时间设置为每天的12:30
但实际执行时间为 12:51
差不多延迟了20分钟
1 2 3 4 on: # 定时执行 schedule: - cron: "30 4 * * *"
查看相关文档后发现 在GitHub中关于Schedule的定义:
Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.
注意: 在高负载的 GitHub action 工作流运行期间,调度事件可能会被延迟。高负载时间包括每个小时的开始。为了减少延迟的机会,请安排您的工作流在一小时的不同时间运行。
也就是说 Schedule中的cron时间并不是真正执行的时候 而是工作流进入到GitHub进行计划排队时间 说简单点就是工作流进入到GitHub执行的队列时间 具体什么时候执行工作流 则需要看GitHub工作流的负载
这个问题在签到需求中并不是致命的问题 如果想要解决可以参考Github Action的 Schedule 运行不准时的解决办法 这篇文章
为啥不用document.cookie
?控制台输入命令获取到的cookie并不完整
这是控制台获取到的cookie
,对比一下接口的cookie
,相差很大
声明📢本项目仅适用于学习交流 并不具备其他用途 也没有经过掘金官方团队 若是被封号 与我无关(手动狗头保命)
有其他想法或功能 欢迎👏进行讨论 如果对你有帮助 给个Star
行不行