From da54a78d6a60dc554354098d444f42482956c99b Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 1 May 2021 22:49:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84shell=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- back/loaders/initData.ts | 39 +- docker/docker-entrypoint.sh | 11 - sample/config.sample.sh | 117 ++++-- sample/notify.js | 760 ++++++++++++++++++++++++++++++++++++ sample/notify.py | 277 +++++++++++++ sample/package.json | 14 + sample/requirements.txt | 1 + shell/code.sh | 153 ++++++++ shell/rmlog.sh | 8 +- shell/share.sh | 109 +++++- shell/task.sh | 16 +- shell/update.sh | 94 +---- src/pages/crontab/index.tsx | 35 +- 14 files changed, 1435 insertions(+), 203 deletions(-) create mode 100644 sample/notify.js create mode 100644 sample/notify.py create mode 100644 sample/package.json create mode 100644 sample/requirements.txt create mode 100644 shell/code.sh diff --git a/README.md b/README.md index 65d0c66f..938b850a 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ python和javaScript的定时任务管理面板 ## 多谢 -* [https://github.com/nevinee/jd_shell](https://github.com/nevinee/jd_shell) +* [nevinee](https://gitee.com/evine) -* [https://github.com/alseambusher/crontab-ui](https://github.com/alseambusher/crontab-ui) +* [crontab-ui](https://github.com/alseambusher/crontab-ui) * [Ant Design](https://ant.design) diff --git a/back/loaders/initData.ts b/back/loaders/initData.ts index 7a39bc0c..65a3a72e 100644 --- a/back/loaders/initData.ts +++ b/back/loaders/initData.ts @@ -5,10 +5,7 @@ import CronService from '../services/cron'; const initData = [ { name: '更新面板', - command: `sleep ${randomSchedule( - 60, - 1, - )} && git_pull >> $QL_DIR/log/git_pull.log 2>&1`, + command: `sleep ${randomSchedule(60, 1)} && ql update`, schedule: `${randomSchedule(60, 1)} ${randomSchedule( 24, 7, @@ -17,44 +14,20 @@ const initData = [ }, { name: 'build面板', - command: 'rebuild >> ${QL_DIR}/log/rebuild.log 2>&1', + command: 'ql rebuild', schedule: '30 7 */7 * *', status: CrontabStatus.disabled, }, - { - name: '自定义仓库', - command: `sleep ${randomSchedule( - 60, - 1, - )} && diy https://ghproxy.com/https://github.com/whyour/hundun.git "quanx/jx|quanx/jd" tokens >> $QL_DIR/log/diy_pull.log 2>&1`, - schedule: `${randomSchedule(60, 1)} ${randomSchedule( - 24, - 6, - ).toString()} * * *`, - status: CrontabStatus.idle, - }, - { - name: '互助码导出', - command: 'export_sharecodes', - schedule: '48 5 * * *', - status: CrontabStatus.idle, - }, { name: '删除日志', - command: 'rm_log >/dev/null 2>&1', + command: 'ql rmlog 7', schedule: '30 7 */7 * *', status: CrontabStatus.disabled, }, { - name: '重置密码', - command: 'js resetpwd', - schedule: '33 6 */7 * *', - status: CrontabStatus.disabled, - }, - { - name: '运行所有脚本(慎用)', - command: 'js runall', - schedule: '33 6 */7 * *', + name: '互助码', + command: 'ql code', + schedule: '30 7 */7 * *', status: CrontabStatus.disabled, }, ]; diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 99257258..e2b5a42e 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -2,18 +2,7 @@ set -e echo -e "======================1. 初始化命令========================\n" -dir_shell=/ql/shell/ -dir_root=$( - cd $dir_shell - cd .. - pwd -) - . $dir_shell/share.sh - -detect_termux -detect_macos -define_cmd link_shell echo diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 919a1d89..960d56fb 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -1,35 +1,95 @@ ## Version: v2.0.0 ## Date: 2021-04-07 -## Update Content: 新一版qinglong。 +## Update Content: 新一版青龙。 ## 上面版本号中,如果第2位数字有变化,那么代表增加了新的参数,如果只有第3位数字有变化,仅代表更新了注释,没有增加新的参数,可更新可不更新 +## 需组合的环境变量列表,env_name需要和var_name一一对应 +env_name=( + JD_COOKIE + FRUITSHARECODES + PETSHARECODES + PLANT_BEAN_SHARECODES + DREAM_FACTORY_SHARE_CODES + DDFACTORY_SHARECODES + JDZZ_SHARECODES + JDJOY_SHARECODES + JXNC_SHARECODES + BOOKSHOP_SHARECODES + JD_CASH_SHARECODES + JDSGMH_SHARECODES + JDCFD_SHARECODES + JDHEALTH_SHARECODES +) +var_name=( + Cookie + ForOtherFruit + ForOtherPet + ForOtherBean + ForOtherDreamFactory + ForOtherJdFactory + ForOtherJdzz + ForOtherJoy + ForOtherJxnc + ForOtherBookShop + ForOtherCash + ForOtherSgmh + ForOtherCfd + ForOtherHealth +) -## 临时屏蔽某个Cookie -## 多个Cookie编号以半角的空格分隔,两侧一对半角双引号,使用此功能后,在运行脚本时账户编号将发生变化 -## 举例1:TempBlockCookie="2" 临时屏蔽掉Cookie2 -## 举例2:TempBlockCookie="2 4" 临时屏蔽掉Cookie2和Cookie4 -TempBlockCookie="" +## 所有有互助码的活动,把脚本名称列在 name_js 中,对应 config.sh 中互助码后缀列在 name_config 中,中文名称列在 name_chinese 中。 +## name_js、name_config 和 name_chinese 中的三个名称必须一一对应。 +name_js=( + jd_fruit + jd_pet + jd_plantBean + jd_dreamFactory + jd_jdfactory + jd_jdzz + jd_crazy_joy + jd_jxnc + jd_bookshop + jd_cash + jd_sgmh + jd_cfd + jd_health +) +name_config=( + Fruit + Pet + Bean + DreamFactory + JdFactory + Jdzz + Joy + Jxnc + BookShop + Cash + Sgmh + Cfd + Health +) +name_chinese=( + 东东农场 + 东东萌宠 + 京东种豆得豆 + 京喜工厂 + 东东工厂 + 京东赚赚 + crazyJoy任务 + 京喜农场 + 口袋书店 + 签到领现金 + 闪购盲盒 + 京喜财富岛 + 东东健康社区 +) -## 如果只是想要屏蔽某个账号不跑某一些脚本,可以参考下面 case 这个命令的例子来控制,case的条件中请输入脚本从scripts目录出发的相对路径,也就是在crontab.list中的task命令后面的脚本路径是什么,这里就填入什么 -## case $1 in -## lxk0301_jd_scripts/jd_fruit.js) -## TempBlockCookie="5" # 账号5不玩lxk0301_jd_scripts下的jd_fruit.js -## ;; -## lxk0301_jd_scripts/jd_dreamFactory.js | whyour_hundun/quanx/didi.js) -## TempBlockCookie="2" # 账号2不玩lxk0301_jd_scripts下的jd_dreamFactory.js和whyour_hundun下子文件夹quanx中的didi.js -## ;; -## lxk0301_jd_scripts/jd_jdzz.js | whyour_hundun/quanx/jx_factory.js) -## TempBlockCookie="3 6" # 账号3、账号6不玩lxk0301_jd_scripts下的jd_jdzz.js和whyour_hundun下子文件夹quanx中的jx_factory.js -## ;; -## esac - - -## 在运行 update 命令时,是否自动删除失效的脚本与定时任务 +## 在运行 ql repo 命令时,是否自动删除失效的脚本与定时任务 AutoDelCron="true" - -## 在运行 update 命令时,是否自动增加新的本地定时任务 +## 在运行 ql repo 命令时,是否自动增加新的本地定时任务 AutoAddCron="true" ## 在运行 task 命令时,随机延迟启动任务的最大延迟时间 @@ -38,7 +98,16 @@ AutoAddCron="true" ## 在crontab.list中,除掉每小时上述时间启动的任务外,其他任务在你定义了 RandomDelay 的情况下,一律启用随机延迟,但如果你给某些任务添加了 "now" 或者 "conc",那么这些任务也将无视随机延迟直接启动 RandomDelay="300" - ## 如果你自己会写shell脚本,并且希望在每次运行 update 命令时,额外运行你的 shell 脚本,请赋值为 "true" ## 同时,请务必将你的脚本命名为 extra.sh (只能叫这个文件名),放在 config 目录下 EnableExtraShell="" + +## 自动按顺序进行账号间互助(选填) 设置为 true 时,将直接导入code最新日志来进行互助 +AutoHelpOther="" + +## 定义 jcode 脚本导出的互助码模板样式(选填) +## 不填 使用“按编号顺序助力模板”,Cookie编号在前的优先助力 +## 填 0 使用“全部一致助力模板”,所有账户要助力的码全部一致 +## 填 1 使用“均等机会助力模板”,所有账户获得助力次数一致 +## 填 2 使用“随机顺序助力模板”,本套脚本内账号间随机顺序助力,每次生成的顺序都不一致。 +HelpType="" diff --git a/sample/notify.js b/sample/notify.js new file mode 100644 index 00000000..0ee8dd25 --- /dev/null +++ b/sample/notify.js @@ -0,0 +1,760 @@ +/* + * @Author: lxk0301 https://gitee.com/lxk0301 + * @Date: 2020-08-19 16:12:40 + * @Last Modified by: whyour + * @Last Modified time: 2021-5-1 15:00:54 + * sendNotify 推送通知功能 + * @param text 通知头 + * @param desp 通知体 + * @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' } + * @param author 作者仓库等信息 例:`本脚本免费使用 By:https://github.com/whyour/qinglong` + */ + +const querystring = require('querystring'); +const $ = new Env(); +const timeout = 15000; //超时时间(单位毫秒) +// =======================================微信server酱通知设置区域=========================================== +//此处填你申请的SCKEY. +//(环境变量名 PUSH_KEY) +let SCKEY = ''; + +// =======================================Bark App通知设置区域=========================================== +//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX) +let BARK_PUSH = ''; +//BARK app推送铃声,铃声列表去APP查看复制填写 +let BARK_SOUND = ''; + +// =======================================telegram机器人通知设置区域=========================================== +//此处填你telegram bot 的Token,telegram机器人通知推送必填项.例如:1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw +//(环境变量名 TG_BOT_TOKEN) +let TG_BOT_TOKEN = ''; +//此处填你接收通知消息的telegram用户的id,telegram机器人通知推送必填项.例如:129xxx206 +//(环境变量名 TG_USER_ID) +let TG_USER_ID = ''; +//tg推送HTTP代理设置(不懂可忽略,telegram机器人通知推送功能中非必填) +let TG_PROXY_HOST = ''; //例如:127.0.0.1(环境变量名:TG_PROXY_HOST) +let TG_PROXY_PORT = ''; //例如:1080(环境变量名:TG_PROXY_PORT) +let TG_PROXY_AUTH = ''; //tg代理配置认证参数 +//Telegram api自建的反向代理地址(不懂可忽略,telegram机器人通知推送功能中非必填),默认tg官方api(环境变量名:TG_API_HOST) +let TG_API_HOST = 'api.telegram.org'; +// =======================================钉钉机器人通知设置区域=========================================== +//此处填你钉钉 bot 的webhook,例如:5a544165465465645d0f31dca676e7bd07415asdasd +//(环境变量名 DD_BOT_TOKEN) +let DD_BOT_TOKEN = ''; +//密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串 +let DD_BOT_SECRET = ''; + +// =======================================企业微信机器人通知设置区域=========================================== +//此处填你企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770),例如:693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa +//(环境变量名 QYWX_KEY) +let QYWX_KEY = ''; + +// =======================================企业微信应用消息通知设置区域=========================================== +/* + 此处填你企业微信应用消息的值(详见文档 https://work.weixin.qq.com/api/doc/90000/90135/90236) + 环境变量名 QYWX_AM依次填入 corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型) + 注意用,号隔开(英文输入法的逗号),例如:wwcff56746d9adwers,B-791548lnzXBE6_BWfxdf3kSTMJr9vFEPKAbh6WERQ,mingcheng,1000001,2COXgjH2UIfERF2zxrtUOKgQ9XklUqMdGSWLBoW_lSDAdafat + 可选推送消息类型(推荐使用图文消息(mpnews)): + - 文本卡片消息: 0 (数字零) + - 文本消息: 1 (数字一) + - 图文消息(mpnews): 素材库图片id, 可查看此教程(http://note.youdao.com/s/HMiudGkb)或者(https://note.youdao.com/ynoteshare1/index.html?id=1a0c8aff284ad28cbd011b29b3ad0191&type=note) + */ +let QYWX_AM = ''; + +// =======================================iGot聚合推送通知设置区域=========================================== +//此处填您iGot的信息(推送key,例如:https://push.hellyw.com/XXXXXXXX) +let IGOT_PUSH_KEY = ''; + +// =======================================push+设置区域======================================= +//官方文档:http://www.pushplus.plus/ +//PUSH_PLUS_TOKEN:微信扫码登录后一对一推送或一对多推送下面的token(您的Token),不提供PUSH_PLUS_USER则默认为一对一推送 +//PUSH_PLUS_USER: 一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送) +let PUSH_PLUS_TOKEN = ''; +let PUSH_PLUS_USER = ''; + +//==========================云端环境变量的判断与接收========================= +if (process.env.PUSH_KEY) { + SCKEY = process.env.PUSH_KEY; +} + +if (process.env.QQ_SKEY) { + QQ_SKEY = process.env.QQ_SKEY; +} + +if (process.env.QQ_MODE) { + QQ_MODE = process.env.QQ_MODE; +} + +if (process.env.BARK_PUSH) { + if ( + process.env.BARK_PUSH.indexOf('https') > -1 || + process.env.BARK_PUSH.indexOf('http') > -1 + ) { + //兼容BARK自建用户 + BARK_PUSH = process.env.BARK_PUSH; + } else { + BARK_PUSH = `https://api.day.app/${process.env.BARK_PUSH}`; + } + if (process.env.BARK_SOUND) { + BARK_SOUND = process.env.BARK_SOUND; + } +} else { + if ( + BARK_PUSH && + BARK_PUSH.indexOf('https') === -1 && + BARK_PUSH.indexOf('http') === -1 + ) { + //兼容BARK本地用户只填写设备码的情况 + BARK_PUSH = `https://api.day.app/${BARK_PUSH}`; + } +} +if (process.env.TG_BOT_TOKEN) { + TG_BOT_TOKEN = process.env.TG_BOT_TOKEN; +} +if (process.env.TG_USER_ID) { + TG_USER_ID = process.env.TG_USER_ID; +} +if (process.env.TG_PROXY_AUTH) TG_PROXY_AUTH = process.env.TG_PROXY_AUTH; +if (process.env.TG_PROXY_HOST) TG_PROXY_HOST = process.env.TG_PROXY_HOST; +if (process.env.TG_PROXY_PORT) TG_PROXY_PORT = process.env.TG_PROXY_PORT; +if (process.env.TG_API_HOST) TG_API_HOST = process.env.TG_API_HOST; + +if (process.env.DD_BOT_TOKEN) { + DD_BOT_TOKEN = process.env.DD_BOT_TOKEN; + if (process.env.DD_BOT_SECRET) { + DD_BOT_SECRET = process.env.DD_BOT_SECRET; + } +} + +if (process.env.QYWX_KEY) { + QYWX_KEY = process.env.QYWX_KEY; +} + +if (process.env.QYWX_AM) { + QYWX_AM = process.env.QYWX_AM; +} + +if (process.env.IGOT_PUSH_KEY) { + IGOT_PUSH_KEY = process.env.IGOT_PUSH_KEY; +} + +if (process.env.PUSH_PLUS_TOKEN) { + PUSH_PLUS_TOKEN = process.env.PUSH_PLUS_TOKEN; +} +if (process.env.PUSH_PLUS_USER) { + PUSH_PLUS_USER = process.env.PUSH_PLUS_USER; +} +//==========================云端环境变量的判断与接收========================= + +/** + * sendNotify 推送通知功能 + * @param text 通知头 + * @param desp 通知体 + * @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' } + * @param author 作者仓库等信息 例:`本脚本免费使用 By:https://github.com/whyour/qinglong` + * @returns {Promise} + */ +async function sendNotify( + text, + desp, + params = {}, + author = '\n\n本脚本免费使用 By:https://github.com/whyour/qinglong', +) { + //提供6种通知 + desp += author; //增加作者信息,防止被贩卖等 + await Promise.all([ + serverNotify(text, desp), //微信server酱 + pushPlusNotify(text, desp), //pushplus(推送加) + ]); + //由于上述两种微信通知需点击进去才能查看到详情,故text(标题内容)携带了账号序号以及昵称信息,方便不点击也可知道是哪个京东哪个活动 + text = text.match(/.*?(?=\s?-)/g) ? text.match(/.*?(?=\s?-)/g)[0] : text; + await Promise.all([ + BarkNotify(text, desp, params), //iOS Bark APP + tgBotNotify(text, desp), //telegram 机器人 + ddBotNotify(text, desp), //钉钉机器人 + qywxBotNotify(text, desp), //企业微信机器人 + qywxamNotify(text, desp), //企业微信应用消息推送 + iGotNotify(text, desp, params), //iGot + ]); +} + +function serverNotify(text, desp, time = 2100) { + return new Promise((resolve) => { + if (SCKEY) { + //微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换 + desp = desp.replace(/[\n\r]/g, '\n\n'); + const options = { + url: SCKEY.includes('SCT') + ? `https://sctapi.ftqq.com/${SCKEY}.send` + : `https://sc.ftqq.com/${SCKEY}.send`, + body: `text=${text}&desp=${desp}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + timeout, + }; + setTimeout(() => { + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('发送通知调用API失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + //server酱和Server酱·Turbo版的返回json格式不太一样 + if (data.errno === 0 || data.data.errno === 0) { + console.log('server酱发送通知消息成功🎉\n'); + } else if (data.errno === 1024) { + // 一分钟内发送相同的内容会触发 + console.log(`server酱发送通知消息异常: ${data.errmsg}\n`); + } else { + console.log( + `server酱发送通知消息异常\n${JSON.stringify(data)}`, + ); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + }, time); + } else { + console.log('\n\n您未提供server酱的SCKEY,取消微信推送消息通知🚫\n'); + resolve(); + } + }); +} + +function CoolPush(text, desp) { + return new Promise((resolve) => { + if (QQ_SKEY) { + let options = { + url: `https://push.xuthus.cc/${QQ_MODE}/${QQ_SKEY}`, + headers: { + 'Content-Type': 'application/json', + }, + }; + + // 已知敏感词 + text = text.replace(/京豆/g, '豆豆'); + desp = desp.replace(/京豆/g, ''); + desp = desp.replace(/🐶/g, ''); + desp = desp.replace(/红包/g, 'H包'); + + switch (QQ_MODE) { + case 'email': + options.json = { + t: text, + c: desp, + }; + break; + default: + options.body = `${text}\n\n${desp}`; + } + + let pushMode = function (t) { + switch (t) { + case 'send': + return '个人'; + case 'group': + return 'QQ群'; + case 'wx': + return '微信'; + case 'ww': + return '企业微信'; + case 'email': + return '邮件'; + default: + return '未知方式'; + } + }; + + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log(`发送${pushMode(QQ_MODE)}通知调用API失败!!\n`); + console.log(err); + } else { + data = JSON.parse(data); + if (data.code === 200) { + console.log(`酷推发送${pushMode(QQ_MODE)}通知消息成功🎉\n`); + } else if (data.code === 400) { + console.log( + `QQ酷推(Cool Push)发送${pushMode(QQ_MODE)}推送失败:${ + data.msg + }\n`, + ); + } else if (data.code === 503) { + console.log(`QQ酷推出错,${data.message}:${data.data}\n`); + } else { + console.log(`酷推推送异常: ${JSON.stringify(data)}`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log('您未提供酷推的SKEY,取消QQ推送消息通知🚫\n'); + resolve(); + } + }); +} + +function BarkNotify(text, desp, params = {}) { + return new Promise((resolve) => { + if (BARK_PUSH) { + const options = { + url: `${BARK_PUSH}/${encodeURIComponent(text)}/${encodeURIComponent( + desp, + )}?sound=${BARK_SOUND}&${querystring.stringify(params)}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + timeout, + }; + $.get(options, (err, resp, data) => { + try { + if (err) { + console.log('Bark APP发送通知调用API失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + if (data.code === 200) { + console.log('Bark APP发送通知消息成功🎉\n'); + } else { + console.log(`${data.message}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(); + } + }); + } else { + console.log('您未提供Bark的APP推送BARK_PUSH,取消Bark推送消息通知🚫\n'); + resolve(); + } + }); +} + +function tgBotNotify(text, desp) { + return new Promise((resolve) => { + if (TG_BOT_TOKEN && TG_USER_ID) { + const options = { + url: `https://${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`, + body: `chat_id=${TG_USER_ID}&text=${text}\n\n${desp}&disable_web_page_preview=true`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + timeout, + }; + if (TG_PROXY_HOST && TG_PROXY_PORT) { + const tunnel = require('tunnel'); + const agent = { + https: tunnel.httpsOverHttp({ + proxy: { + host: TG_PROXY_HOST, + port: TG_PROXY_PORT * 1, + proxyAuth: TG_PROXY_AUTH, + }, + }), + }; + Object.assign(options, { agent }); + } + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('telegram发送通知消息失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + if (data.ok) { + console.log('Telegram发送通知消息成功🎉。\n'); + } else if (data.error_code === 400) { + console.log( + '请主动给bot发送一条消息并检查接收用户ID是否正确。\n', + ); + } else if (data.error_code === 401) { + console.log('Telegram bot token 填写错误。\n'); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log( + '您未提供telegram机器人推送所需的TG_BOT_TOKEN和TG_USER_ID,取消telegram推送消息通知🚫\n', + ); + resolve(); + } + }); +} +function ddBotNotify(text, desp) { + return new Promise((resolve) => { + const options = { + url: `https://oapi.dingtalk.com/robot/send?access_token=${DD_BOT_TOKEN}`, + json: { + msgtype: 'text', + text: { + content: ` ${text}\n\n${desp}`, + }, + }, + headers: { + 'Content-Type': 'application/json', + }, + timeout, + }; + if (DD_BOT_TOKEN && DD_BOT_SECRET) { + const crypto = require('crypto'); + const dateNow = Date.now(); + const hmac = crypto.createHmac('sha256', DD_BOT_SECRET); + hmac.update(`${dateNow}\n${DD_BOT_SECRET}`); + const result = encodeURIComponent(hmac.digest('base64')); + options.url = `${options.url}×tamp=${dateNow}&sign=${result}`; + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('钉钉发送通知消息失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + if (data.errcode === 0) { + console.log('钉钉发送通知消息成功🎉。\n'); + } else { + console.log(`${data.errmsg}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else if (DD_BOT_TOKEN) { + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('钉钉发送通知消息失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + if (data.errcode === 0) { + console.log('钉钉发送通知消息完成。\n'); + } else { + console.log(`${data.errmsg}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log( + '您未提供钉钉机器人推送所需的DD_BOT_TOKEN或者DD_BOT_SECRET,取消钉钉推送消息通知🚫\n', + ); + resolve(); + } + }); +} + +function qywxBotNotify(text, desp) { + return new Promise((resolve) => { + const options = { + url: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${QYWX_KEY}`, + json: { + msgtype: 'text', + text: { + content: ` ${text}\n\n${desp}`, + }, + }, + headers: { + 'Content-Type': 'application/json', + }, + timeout, + }; + if (QYWX_KEY) { + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('企业微信发送通知消息失败!!\n'); + console.log(err); + } else { + data = JSON.parse(data); + if (data.errcode === 0) { + console.log('企业微信发送通知消息成功🎉。\n'); + } else { + console.log(`${data.errmsg}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log( + '您未提供企业微信机器人推送所需的QYWX_KEY,取消企业微信推送消息通知🚫\n', + ); + resolve(); + } + }); +} + +function ChangeUserId(desp) { + const QYWX_AM_AY = QYWX_AM.split(','); + if (QYWX_AM_AY[2]) { + const userIdTmp = QYWX_AM_AY[2].split('|'); + let userId = ''; + for (let i = 0; i < userIdTmp.length; i++) { + const count = '账号' + (i + 1); + const count2 = '签到号 ' + (i + 1); + if (desp.match(count2)) { + userId = userIdTmp[i]; + } + } + if (!userId) userId = QYWX_AM_AY[2]; + return userId; + } else { + return '@all'; + } +} + +function qywxamNotify(text, desp) { + return new Promise((resolve) => { + if (QYWX_AM) { + const QYWX_AM_AY = QYWX_AM.split(','); + const options_accesstoken = { + url: `https://qyapi.weixin.qq.com/cgi-bin/gettoken`, + json: { + corpid: `${QYWX_AM_AY[0]}`, + corpsecret: `${QYWX_AM_AY[1]}`, + }, + headers: { + 'Content-Type': 'application/json', + }, + timeout, + }; + $.post(options_accesstoken, (err, resp, data) => { + html = desp.replace(/\n/g, '
'); + var json = JSON.parse(data); + accesstoken = json.access_token; + let options; + + switch (QYWX_AM_AY[4]) { + case '0': + options = { + msgtype: 'textcard', + textcard: { + title: `${text}`, + description: `${desp}`, + url: 'https://github.com/whyour/qinglong', + btntxt: '更多', + }, + }; + break; + + case '1': + options = { + msgtype: 'text', + text: { + content: `${text}\n\n${desp}`, + }, + }; + break; + + default: + options = { + msgtype: 'mpnews', + mpnews: { + articles: [ + { + title: `${text}`, + thumb_media_id: `${QYWX_AM_AY[4]}`, + author: `智能助手`, + content_source_url: ``, + content: `${html}`, + digest: `${desp}`, + }, + ], + }, + }; + } + if (!QYWX_AM_AY[4]) { + //如不提供第四个参数,则默认进行文本消息类型推送 + options = { + msgtype: 'text', + text: { + content: `${text}\n\n${desp}`, + }, + }; + } + options = { + url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accesstoken}`, + json: { + touser: `${ChangeUserId(desp)}`, + agentid: `${QYWX_AM_AY[3]}`, + safe: '0', + ...options, + }, + headers: { + 'Content-Type': 'application/json', + }, + }; + + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log( + '成员ID:' + + ChangeUserId(desp) + + '企业微信应用消息发送通知消息失败!!\n', + ); + console.log(err); + } else { + data = JSON.parse(data); + if (data.errcode === 0) { + console.log( + '成员ID:' + + ChangeUserId(desp) + + '企业微信应用消息发送通知消息成功🎉。\n', + ); + } else { + console.log(`${data.errmsg}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + }); + } else { + console.log( + '您未提供企业微信应用消息推送所需的QYWX_AM,取消企业微信应用消息推送消息通知🚫\n', + ); + resolve(); + } + }); +} + +function iGotNotify(text, desp, params = {}) { + return new Promise((resolve) => { + if (IGOT_PUSH_KEY) { + // 校验传入的IGOT_PUSH_KEY是否有效 + const IGOT_PUSH_KEY_REGX = new RegExp('^[a-zA-Z0-9]{24}$'); + if (!IGOT_PUSH_KEY_REGX.test(IGOT_PUSH_KEY)) { + console.log('您所提供的IGOT_PUSH_KEY无效\n'); + resolve(); + return; + } + const options = { + url: `https://push.hellyw.com/${IGOT_PUSH_KEY.toLowerCase()}`, + body: `title=${text}&content=${desp}&${querystring.stringify(params)}`, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + timeout, + }; + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log('发送通知调用API失败!!\n'); + console.log(err); + } else { + if (typeof data === 'string') data = JSON.parse(data); + if (data.ret === 0) { + console.log('iGot发送通知消息成功🎉\n'); + } else { + console.log(`iGot发送通知消息失败:${data.errMsg}\n`); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log('您未提供iGot的推送IGOT_PUSH_KEY,取消iGot推送消息通知🚫\n'); + resolve(); + } + }); +} + +function pushPlusNotify(text, desp) { + return new Promise((resolve) => { + if (PUSH_PLUS_TOKEN) { + desp = desp.replace(/[\n\r]/g, '
'); // 默认为html, 不支持plaintext + const body = { + token: `${PUSH_PLUS_TOKEN}`, + title: `${text}`, + content: `${desp}`, + topic: `${PUSH_PLUS_USER}`, + }; + const options = { + url: `http://www.pushplus.plus/send`, + body: JSON.stringify(body), + headers: { + 'Content-Type': ' application/json', + }, + timeout, + }; + $.post(options, (err, resp, data) => { + try { + if (err) { + console.log( + `push+发送${ + PUSH_PLUS_USER ? '一对多' : '一对一' + }通知消息失败!!\n`, + ); + console.log(err); + } else { + data = JSON.parse(data); + if (data.code === 200) { + console.log( + `push+发送${ + PUSH_PLUS_USER ? '一对多' : '一对一' + }通知消息完成。\n`, + ); + } else { + console.log( + `push+发送${ + PUSH_PLUS_USER ? '一对多' : '一对一' + }通知消息失败:${data.msg}\n`, + ); + } + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(data); + } + }); + } else { + console.log( + '您未提供push+推送所需的PUSH_PLUS_TOKEN,取消push+推送消息通知🚫\n', + ); + resolve(); + } + }); +} + +module.exports = { + sendNotify, + BARK_PUSH, +}; +// prettier-ignore +function Env(t,s){return new class{constructor(t,s){this.name=t,this.data=null,this.dataFile="box.dat",this.logs=[],this.logSeparator="\n",this.startTime=(new Date).getTime(),Object.assign(this,s),this.log("",`\ud83d\udd14${this.name}, \u5f00\u59cb!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}getScript(t){return new Promise(s=>{$.get({url:t},(t,e,i)=>s(i))})}runScript(t,s){return new Promise(e=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let o=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");o=o?1*o:20,o=s&&s.timeout?s.timeout:o;const[h,a]=i.split("@"),r={url:`http://${a}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:o},headers:{"X-Key":h,Accept:"*/*"}};$.post(r,(t,s,i)=>e(i))}).catch(t=>this.logErr(t))}loaddata(){if(!this.isNode())return{};{this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),s=this.path.resolve(process.cwd(),this.dataFile),e=this.fs.existsSync(t),i=!e&&this.fs.existsSync(s);if(!e&&!i)return{};{const i=e?t:s;try{return JSON.parse(this.fs.readFileSync(i))}catch(t){return{}}}}}writedata(){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),s=this.path.resolve(process.cwd(),this.dataFile),e=this.fs.existsSync(t),i=!e&&this.fs.existsSync(s),o=JSON.stringify(this.data);e?this.fs.writeFileSync(t,o):i?this.fs.writeFileSync(s,o):this.fs.writeFileSync(t,o)}}lodash_get(t,s,e){const i=s.replace(/\[(\d+)\]/g,".$1").split(".");let o=t;for(const t of i)if(o=Object(o)[t],void 0===o)return e;return o}lodash_set(t,s,e){return Object(t)!==t?t:(Array.isArray(s)||(s=s.toString().match(/[^.[\]]+/g)||[]),s.slice(0,-1).reduce((t,e,i)=>Object(t[e])===t[e]?t[e]:t[e]=Math.abs(s[i+1])>>0==+s[i+1]?[]:{},t)[s[s.length-1]]=e,t)}getdata(t){let s=this.getval(t);if(/^@/.test(t)){const[,e,i]=/^@(.*?)\.(.*?)$/.exec(t),o=e?this.getval(e):"";if(o)try{const t=JSON.parse(o);s=t?this.lodash_get(t,i,""):s}catch(t){s=""}}return s}setdata(t,s){let e=!1;if(/^@/.test(s)){const[,i,o]=/^@(.*?)\.(.*?)$/.exec(s),h=this.getval(i),a=i?"null"===h?null:h||"{}":"{}";try{const s=JSON.parse(a);this.lodash_set(s,o,t),e=this.setval(JSON.stringify(s),i)}catch(s){const h={};this.lodash_set(h,o,t),e=this.setval(JSON.stringify(h),i)}}else e=$.setval(t,s);return e}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,s){return this.isSurge()||this.isLoon()?$persistentStore.write(t,s):this.isQuanX()?$prefs.setValueForKey(t,s):this.isNode()?(this.data=this.loaddata(),this.data[s]=t,this.writedata(),!0):this.data&&this.data[s]||null}initGotEnv(t){this.got=this.got?this.got:require("got"),this.cktough=this.cktough?this.cktough:require("tough-cookie"),this.ckjar=this.ckjar?this.ckjar:new this.cktough.CookieJar,t&&(t.headers=t.headers?t.headers:{},void 0===t.headers.Cookie&&void 0===t.cookieJar&&(t.cookieJar=this.ckjar))}get(t,s=(()=>{})){t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon()?$httpClient.get(t,(t,e,i)=>{!t&&e&&(e.body=i,e.statusCode=e.status),s(t,e,i)}):this.isQuanX()?$task.fetch(t).then(t=>{const{statusCode:e,statusCode:i,headers:o,body:h}=t;s(null,{status:e,statusCode:i,headers:o,body:h},h)},t=>s(t)):this.isNode()&&(this.initGotEnv(t),this.got(t).on("redirect",(t,s)=>{try{const e=t.headers["set-cookie"].map(this.cktough.Cookie.parse).toString();this.ckjar.setCookieSync(e,null),s.cookieJar=this.ckjar}catch(t){this.logErr(t)}}).then(t=>{const{statusCode:e,statusCode:i,headers:o,body:h}=t;s(null,{status:e,statusCode:i,headers:o,body:h},h)},t=>s(t)))}post(t,s=(()=>{})){if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),delete t.headers["Content-Length"],this.isSurge()||this.isLoon())$httpClient.post(t,(t,e,i)=>{!t&&e&&(e.body=i,e.statusCode=e.status),s(t,e,i)});else if(this.isQuanX())t.method="POST",$task.fetch(t).then(t=>{const{statusCode:e,statusCode:i,headers:o,body:h}=t;s(null,{status:e,statusCode:i,headers:o,body:h},h)},t=>s(t));else if(this.isNode()){this.initGotEnv(t);const{url:e,...i}=t;this.got.post(e,i).then(t=>{const{statusCode:e,statusCode:i,headers:o,body:h}=t;s(null,{status:e,statusCode:i,headers:o,body:h},h)},t=>s(t))}}time(t){let s={"M+":(new Date).getMonth()+1,"d+":(new Date).getDate(),"H+":(new Date).getHours(),"m+":(new Date).getMinutes(),"s+":(new Date).getSeconds(),"q+":Math.floor(((new Date).getMonth()+3)/3),S:(new Date).getMilliseconds()};/(y+)/.test(t)&&(t=t.replace(RegExp.$1,((new Date).getFullYear()+"").substr(4-RegExp.$1.length)));for(let e in s)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?s[e]:("00"+s[e]).substr((""+s[e]).length)));return t}msg(s=t,e="",i="",o){const h=t=>!t||!this.isLoon()&&this.isSurge()?t:"string"==typeof t?this.isLoon()?t:this.isQuanX()?{"open-url":t}:void 0:"object"==typeof t&&(t["open-url"]||t["media-url"])?this.isLoon()?t["open-url"]:this.isQuanX()?t:void 0:void 0;$.isMute||(this.isSurge()||this.isLoon()?$notification.post(s,e,i,h(o)):this.isQuanX()&&$notify(s,e,i,h(o))),this.logs.push("","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="),this.logs.push(s),e&&this.logs.push(e),i&&this.logs.push(i)}log(...t){t.length>0?this.logs=[...this.logs,...t]:console.log(this.logs.join(this.logSeparator))}logErr(t,s){const e=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();e?$.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t.stack):$.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t)}wait(t){return new Promise(s=>setTimeout(s,t))}done(t={}){const s=(new Date).getTime(),e=(s-this.startTime)/1e3;this.log("",`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${e} \u79d2`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,s)} diff --git a/sample/notify.py b/sample/notify.py new file mode 100644 index 00000000..3106710e --- /dev/null +++ b/sample/notify.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# _*_ coding:utf-8 _*_ + +import sys +import os +cur_path = os.path.abspath(os.path.dirname(__file__)) +root_path = os.path.split(cur_path)[0] +sys.path.append(root_path) +import requests +import json +import traceback +import time +import hmac +import hashlib +import base64 +import urllib.parse +from requests.adapters import HTTPAdapter +from urllib3.util import Retry +import re + +# 通知服务 +BARK = '' # bark服务,此参数如果以http或者https开头则判定为自建bark服务; secrets可填; +SCKEY = '' # Server酱的SCKEY; secrets可填 +TG_BOT_TOKEN = '' # tg机器人的TG_BOT_TOKEN; secrets可填 +TG_USER_ID = '' # tg机器人的TG_USER_ID; secrets可填 +TG_PROXY_IP = '' # tg机器人的TG_PROXY_IP; secrets可填 +TG_PROXY_PORT = '' # tg机器人的TG_PROXY_PORT; secrets可填 +DD_BOT_ACCESS_TOKEN = '' # 钉钉机器人的DD_BOT_ACCESS_TOKEN; secrets可填 +DD_BOT_SECRET = '' # 钉钉机器人的DD_BOT_SECRET; secrets可填 +QYWX_APP = '' # 企业微信应用的QYWX_APP; secrets可填 参考http://note.youdao.com/s/HMiudGkb + +notify_mode = [] + +# GitHub action运行需要填写对应的secrets +if "BARK" in os.environ and os.environ["BARK"]: + BARK = os.environ["BARK"] +if "SCKEY" in os.environ and os.environ["SCKEY"]: + SCKEY = os.environ["SCKEY"] +if "TG_BOT_TOKEN" in os.environ and os.environ["TG_BOT_TOKEN"] and "TG_USER_ID" in os.environ and os.environ["TG_USER_ID"]: + TG_BOT_TOKEN = os.environ["TG_BOT_TOKEN"] + TG_USER_ID = os.environ["TG_USER_ID"] +if "DD_BOT_ACCESS_TOKEN" in os.environ and os.environ["DD_BOT_ACCESS_TOKEN"] and "DD_BOT_SECRET" in os.environ and os.environ["DD_BOT_SECRET"]: + DD_BOT_ACCESS_TOKEN = os.environ["DD_BOT_ACCESS_TOKEN"] + DD_BOT_SECRET = os.environ["DD_BOT_SECRET"] +if "QYWX_APP" in os.environ and os.environ["QYWX_APP"]: + QYWX_APP = os.environ["QYWX_APP"] + +if BARK: + notify_mode.append('bark') + print("BARK 推送打开") +if SCKEY: + notify_mode.append('sc_key') + print("Server酱 推送打开") +if TG_BOT_TOKEN and TG_USER_ID: + notify_mode.append('telegram_bot') + print("Telegram 推送打开") +if DD_BOT_ACCESS_TOKEN and DD_BOT_SECRET: + notify_mode.append('dingding_bot') + print("钉钉机器人 推送打开") +if QYWX_APP: + notify_mode.append('qywxapp_bot') + print("企业微信应用 推送打开") + +def bark(title, content): + print("\n") + if not BARK: + print("bark服务的bark_token未设置!!\n取消推送") + return + print("bark服务启动") + url = None + if BARK.startswith('http'): + url = f"""{BARK}/{title}/{content}""" + else: + url = f"""https://api.day.app/{BARK}/{title}/{content}""" + response = requests.get(url).json() + if response['code'] == 200: + print('推送成功!') + else: + print('推送失败!') + +def serverJ(title, content): + print("\n") + if not SCKEY: + print("server酱服务的SCKEY未设置!!\n取消推送") + return + print("serverJ服务启动") + data = { + "text": title, + "desp": content.replace("\n", "\n\n") + } + response = requests.post(f"https://sc.ftqq.com/{SCKEY}.send", data=data).json() + if response['errno'] == 0: + print('推送成功!') + else: + print('推送失败!') + +def telegram_bot(title, content): + print("\n") + bot_token = TG_BOT_TOKEN + user_id = TG_USER_ID + if not bot_token or not user_id: + print("tg服务的bot_token或者user_id未设置!!\n取消推送") + return + print("tg服务启动") + url=f"https://api.telegram.org/bot{TG_BOT_TOKEN}/sendMessage" + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + payload = {'chat_id': str(TG_USER_ID), 'text': f'{title}\n\n{content}', 'disable_web_page_preview': 'true'} + proxies = None + if TG_PROXY_IP and TG_PROXY_PORT: + proxyStr = "http://{}:{}".format(TG_PROXY_IP, TG_PROXY_PORT) + proxies = {"http": proxyStr, "https": proxyStr} + response = requests.post(url=url, headers=headers, params=payload, proxies=proxies).json() + if response['ok']: + print('推送成功!') + else: + print('推送失败!') + +def dingding_bot(title, content): + timestamp = str(round(time.time() * 1000)) # 时间戳 + secret_enc = DD_BOT_SECRET.encode('utf-8') + string_to_sign = '{}\n{}'.format(timestamp, DD_BOT_SECRET) + string_to_sign_enc = string_to_sign.encode('utf-8') + hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() + sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) # 签名 + print('开始使用 钉钉机器人 推送消息...', end='') + url = f'https://oapi.dingtalk.com/robot/send?access_token={DD_BOT_ACCESS_TOKEN}×tamp={timestamp}&sign={sign}' + headers = {'Content-Type': 'application/json;charset=utf-8'} + data = { + 'msgtype': 'text', + 'text': {'content': f'{title}\n\n{content}'} + } + response = requests.post(url=url, data=json.dumps(data), headers=headers, timeout=15).json() + if not response['errcode']: + print('推送成功!') + else: + print('推送失败!') + +def qywxapp_bot(title, content): + print("\n") + if not QYWX_APP: + print("企业微信应用的QYWX_APP未设置!!\n取消推送") + return + print("企业微信应用启动") + qywx_app_params = QYWX_APP.split(',') + url='https://qyapi.weixin.qq.com/cgi-bin/gettoken' + headers= { + 'Content-Type': 'application/json', + } + payload = { + 'corpid': qywx_app_params[0], + 'corpsecret': qywx_app_params[1], + } + response = requests.post(url=url, headers=headers, data=json.dumps(payload), timeout=15).json() + accesstoken = response["access_token"] + html = content.replace("\n", "
") + + options = None + if not qywx_app_params[4]: + options = { + 'msgtype': 'text', + 'text': { + content: f'{title}\n\n${content}' + } + } + elif qywx_app_params[4] == '0': + options = { + 'msgtype': 'textcard', + 'textcard': { + title: f'{title}', + description: f'{content}', + btntxt: '更多' + } + } + elif qywx_app_params[4] == '1': + options = { + 'msgtype': 'text', + 'text': { + content: f'{title}\n\n${content}' + } + } + else: + options = { + 'msgtype': 'mpnews', + 'mpnews': { + 'articles': [ + { + 'title': f'{title}', + 'thumb_media_id': f'{qywx_app_params[4]}', + 'author': '智能助手', + 'content_source_url': '', + 'content': f'{html}', + 'digest': f'{content}' + } + ] + } + } + + url=f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={accesstoken}" + data = { + 'touser': f'{change_user_id(content)}', + 'agentid': f'{qywx_app_params[3]}', + 'safe': '0' + } + data.update(options) + headers = { + 'Content-Type': 'application/json', + } + response = requests.post(url=url, headers=headers, data=json.dumps(data)).json() + + if response['errcode'] == 0: + print('推送成功!') + else: + print('推送失败!') + +def change_user_id(desp): + qywx_app_params = QYWX_APP.split(',') + if qywx_app_params[2]: + userIdTmp = qywx_app_params[2].split("|") + userId = "" + for i in range(len(userIdTmp)): + count1 = f"账号{i + 1}" + count2 = f"签到号{i + 1}" + if re.search(count1, desp) or re.search(count2, desp): + userId = userIdTmp[i] + if not userId: + userId = qywx_app_params[2] + return userId + else: + return "@all" + +def send(title, content): + """ + 使用 bark, telegram bot, dingding bot, serverJ 发送手机推送 + :param title: + :param content: + :return: + """ + for i in notify_mode: + if i == 'bark': + if BARK: + bark(title=title, content=content) + else: + print('未启用 bark') + continue + if i == 'sc_key': + if SCKEY: + serverJ(title=title, content=content) + else: + print('未启用 Server酱') + continue + elif i == 'dingding_bot': + if DD_BOT_ACCESS_TOKEN and DD_BOT_SECRET: + dingding_bot(title=title, content=content) + else: + print('未启用 钉钉机器人') + continue + elif i == 'telegram_bot': + if TG_BOT_TOKEN and TG_USER_ID: + telegram_bot(title=title, content=content) + else: + print('未启用 telegram机器人') + continue + elif i == 'qywxapp_bot': + if QYWX_APP: + qywxapp_bot(title=title, content=content) + else: + print('未启用 企业微信应用推送') + continue + else: + print('此类推送方式不存在') + +def main(): + send('title', 'content') + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/sample/package.json b/sample/package.json new file mode 100644 index 00000000..3b6deacf --- /dev/null +++ b/sample/package.json @@ -0,0 +1,14 @@ +{ + "name": "dependence", + "author": "", + "license": "ISC", + "crypto-js": "^4.0.0", + "download": "^8.0.0", + "got": "^11.5.1", + "http-server": "^0.12.3", + "qrcode-terminal": "^0.12.0", + "request": "^2.88.2", + "tough-cookie": "^4.0.0", + "tunnel": "0.0.6", + "ws": "^7.4.3" +} \ No newline at end of file diff --git a/sample/requirements.txt b/sample/requirements.txt new file mode 100644 index 00000000..663bd1f6 --- /dev/null +++ b/sample/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/shell/code.sh b/shell/code.sh new file mode 100644 index 00000000..b9fcf469 --- /dev/null +++ b/shell/code.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash + +## 导入通用变量与函数 +. $dir_shell/share.sh + +## 导入配置文件 +import_config_and_check + +## 生成pt_pin清单 +gen_pt_pin_array () { + local tmp1 tmp2 i pt_pin_temp + for ((user_num=1; user_num<=$user_sum; user_num++)); do + tmp1=Cookie$user_num + tmp2=${!tmp1} + i=$(($user_num - 1)) + pt_pin_temp=$(echo $tmp2 | perl -pe "{s|.*pt_pin=([^; ]+)(?=;?).*|\1|; s|%|\\\x|g}") + [[ $pt_pin_temp == *\\x* ]] && pt_pin[i]=$(printf $pt_pin_temp) || pt_pin[i]=$pt_pin_temp + done +} + +## 导出互助码的通用程序,$1:去掉后缀的脚本名称,$2:config.sh中的后缀,$3:活动中文名称 +export_codes_sub () { + local task_name=$1 + local config_name=$2 + local chinese_name=$3 + local config_name_my=My$config_name + local config_name_for_other=ForOther$config_name + local i j k m n pt_pin_in_log code tmp_grep tmp_my_code tmp_for_other user_num random_num_list + if cd $dir_log/$task_name &>/dev/null && [[ $(ls) ]]; then + ## 寻找所有互助码以及对应的pt_pin + i=0 + pt_pin_in_log=() + code=() + pt_pin_and_code=$(ls -r *.log | xargs awk -v var="的$chinese_name好友互助码" 'BEGIN{FS="[( )】]+"; OFS="&"} $3~var {print $2,$4}') + for line in $pt_pin_and_code; do + pt_pin_in_log[i]=$(echo $line | awk -F "&" '{print $1}') + code[i]=$(echo $line | awk -F "&" '{print $2}') + let i++ + done + + ## 输出My系列变量 + if [[ ${#code[*]} -gt 0 ]]; then + for ((m=0; m<${#pt_pin[*]}; m++)); do + tmp_my_code="" + j=$((m + 1)) + for ((n=0; n<${#code[*]}; n++)); do + if [[ ${pt_pin[m]} == ${pt_pin_in_log[n]} ]]; then + tmp_my_code=${code[n]} + break + fi + done + echo "$config_name_my$j='$tmp_my_code'" + done + else + echo "## 从日志中未找到任何互助码" + fi + + ## 输出ForOther系列变量 + if [[ ${#code[*]} -gt 0 ]]; then + echo + case $HelpType in + 0) ## 全部一致 + tmp_for_other="" + for ((m=0; m<${#pt_pin[*]}; m++)); do + j=$((m + 1)) + tmp_for_other="$tmp_for_other@\${$config_name_my$j}" + done + echo "${config_name_for_other}1=\"$tmp_for_other\"" | perl -pe "s|($config_name_for_other\d+=\")@|\1|" + for ((m=1; m<${#pt_pin[*]}; m++)); do + j=$((m + 1)) + echo "$config_name_for_other$j=\"\${${config_name_for_other}1}\"" + done + ;; + + 1) ## 均等助力 + for ((m=0; m<${#pt_pin[*]}; m++)); do + tmp_for_other="" + j=$((m + 1)) + for ((n=$m; n<$(($user_sum + $m)); n++)); do + [[ $m -eq $n ]] && continue + if [[ $((n + 1)) -le $user_sum ]]; then + k=$((n + 1)) + else + k=$((n + 1 - $user_sum)) + fi + tmp_for_other="$tmp_for_other@\${$config_name_my$k}" + done + echo "$config_name_for_other$j=\"$tmp_for_other\"" | perl -pe "s|($config_name_for_other\d+=\")@|\1|" + done + ;; + + 2) ## 本套脚本内账号间随机顺序助力 + for ((m=0; m<${#pt_pin[*]}; m++)); do + tmp_for_other="" + random_num_list=$(seq $user_sum | sort -R) + j=$((m + 1)) + for n in $random_num_list; do + [[ $j -eq $n ]] && continue + tmp_for_other="$tmp_for_other@\${$config_name_my$n}" + done + echo "$config_name_for_other$j=\"$tmp_for_other\"" | perl -pe "s|($config_name_for_other\d+=\")@|\1|" + done + ;; + + *) ## 按编号优先 + for ((m=0; m<${#pt_pin[*]}; m++)); do + tmp_for_other="" + j=$((m + 1)) + for ((n=0; n<${#pt_pin[*]}; n++)); do + [[ $m -eq $n ]] && continue + k=$((n + 1)) + tmp_for_other="$tmp_for_other@\${$config_name_my$k}" + done + echo "$config_name_for_other$j=\"$tmp_for_other\"" | perl -pe "s|($config_name_for_other\d+=\")@|\1|" + done + ;; + esac + fi + else + echo "## 未运行过 $task_name.js 脚本,未产生日志" + fi +} + +## 汇总输出 +export_all_codes () { + gen_pt_pin_array + echo -e "\n# 从日志提取互助码,编号和配置文件中Cookie编号完全对应,如果为空就是所有日志中都没有。\n\n# 即使某个MyXxx变量未赋值,也可以将其变量名填在ForOtherXxx中,jtask脚本会自动过滤空值。\n" + echo -n "# 你选择的互助码模板为:" + case $HelpType in + 0) + echo "所有账号助力码全部一致。" + ;; + 1) + echo "所有账号机会均等助力。" + ;; + 2) + echo "本套脚本内账号间随机顺序助力。" + ;; + *) + echo "按账号编号优先。" + ;; + esac + for ((i=0; i<${#name_js[*]}; i++)); do + echo -e "\n## ${name_chinese[i]}:" + export_codes_sub "${name_js[i]}" "${name_config[i]}" "${name_chinese[i]}" + done +} + +## 执行并写入日志 +log_time=$(date "+%Y-%m-%d-%H-%M-%S") +log_path="$dir_code/$log_time.log" +make_dir "$dir_code" +export_all_codes | perl -pe "{s|京东种豆|种豆|; s|crazyJoy任务|疯狂的JOY|}" | tee $log_path \ No newline at end of file diff --git a/shell/rmlog.sh b/shell/rmlog.sh index 69dbd310..3a77ecbf 100755 --- a/shell/rmlog.sh +++ b/shell/rmlog.sh @@ -1,16 +1,10 @@ #!/usr/bin/env bash -## 判断环境 -dir_shell=$(dirname $(readlink -f "$0")) -dir_root=$(cd $dir_shell; pwd) - ## 导入通用变量与函数 . $dir_shell/share.sh -## 导入配置文件,检测平台 +## 导入配置文件 import_config_no_check rmlog -detect_termux -detect_macos days=$1 diff --git a/shell/share.sh b/shell/share.sh index 44d92348..e4ee18a3 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -1,13 +1,16 @@ ## 目录 +dir_root=/ql +dir_shell=$dir_root/shell dir_sample=$dir_root/sample dir_config=$dir_root/config dir_scripts=$dir_root/scripts dir_repo=$dir_root/repo -dir_raw=$dir_scripts/raw +dir_raw=$dir_root/raw dir_log=$dir_root/log dir_db=$dir_root/db dir_manual_log=$dir_root/manual_log dir_list_tmp=$dir_log/.tmp +dir_code=$dir_log/code ## 文件 file_config_sample=$dir_sample/config.sample.sh @@ -17,6 +20,10 @@ file_config_user=$dir_config/config.sh file_auth_sample=$dir_sample/auth.sample.json file_auth_user=$dir_config/auth.json file_extra_shell=$dir_config/extra.sh +file_notify_js_sample=$dir_sample/notify.js +file_notify_py_sample=$dir_sample/notify.py +file_notify_py=$dir_scripts/notify.py +file_notify_js=$dir_scripts/sendNotify.js ## 清单文件 list_crontab_user=$dir_config/crontab.list @@ -26,14 +33,6 @@ list_own_user=$dir_list_tmp/own_user.list list_own_add=$dir_list_tmp/own_add.list list_own_drop=$dir_list_tmp/own_drop.list -## 需组合的环境变量列表,env_name需要和var_name一一对应,需要从api取信息 -env_name=( - JD_COOKIE -) -var_name=( - Cookie -) - ## 软连接及其原始文件对应关系 link_name=( task @@ -50,9 +49,9 @@ import_config_no_check () { [ -f $file_config_user ] && . $file_config_user } -## 导入配置文件并校验,$1:任务名称 +## 导入配置文件并校验 import_config_and_check () { - import_config_no_check $1 + import_config_no_check if [[ ! -s $file_cookie ]]; then echo -e "请先配置好Cookie...\n" exit 1 @@ -60,7 +59,6 @@ import_config_and_check () { user_sum=0 for line in $(cat $file_cookie); do let user_sum++ - [[ $user_sum -gt $((3 * 5)) ]] && break eval Cookie${user_sum}="\"$line\"" done fi @@ -148,6 +146,7 @@ fix_config () { make_dir $dir_log make_dir $dir_db make_dir $dir_manual_log + make_dir $dir_scripts if [ ! -s $file_config_user ]; then echo -e "复制一份 $file_config_sample 为 $file_config_user,随后请按注释编辑你的配置文件:$file_config_user\n" @@ -167,9 +166,95 @@ fix_config () { echo fi + if [ ! -s $file_notify_py ]; then + echo -e "复制一份 $file_notify_py_sample 为 $file_notify_py\n" + cp -fv $file_notify_py_sample $file_notify_py + echo + fi + + if [ ! -s $file_notify_js ]; then + echo -e "复制一份 $file_notify_js_sample 为 $file_notify_js\n" + cp -fv $file_notify_js_sample $file_notify_js + echo + fi + if [ -s /etc/nginx/conf.d/default.conf ]; then echo -e "检测到默认nginx配置文件,删除...\n" rm -f /etc/nginx/conf.d/default.conf echo fi } + +## npm install 子程序,判断是否为安卓,判断是否安装有yarn +npm_install_sub() { + if [ $is_termux -eq 1 ]; then + npm install --production --no-save --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links --no-save + elif ! type yarn >/dev/null 2>&1; then + npm install --production --no-save --registry=https://registry.npm.taobao.org || npm install --production --no-save + else + echo -e "检测到本机安装了 yarn,使用 yarn 替代 npm...\n" + yarn install --production --network-timeout 1000000000 --registry=https://registry.npm.taobao.org || yarn install --production --network-timeout 1000000000 + fi +} + +## npm install,$1:package.json文件所在路径 +npm_install_1() { + local dir_current=$(pwd) + local dir_work=$1 + + cd $dir_work + echo -e "运行 npm install...\n" + npm_install_sub + [[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n" + cd $dir_current +} + +npm_install_2() { + local dir_current=$(pwd) + local dir_work=$1 + + cd $dir_work + echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n" + npm_install_sub + [[ $? -ne 0 ]] && echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n" + npm_install_1 $dir_work + cd $dir_current +} + +## 比对两个文件,$1比$2新时,将$1复制为$2 +diff_and_copy() { + local copy_source=$1 + local copy_to=$2 + if [ ! -s $copy_to ] || [[ $(diff $copy_source $copy_to) ]]; then + cp -f $copy_source $copy_to + fi +} + +## 更新依赖 +update_depend() { + local dir_current=$(pwd) + + if [ ! -s $dir_scripts/package.json ] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then + cp -f $dir_sample/package.json $dir_scripts/package.json + npm_install_2 $dir_scripts + fi + + [ ! -d $dir_scripts/node_modules ] && npm_install_2 $dir_scripts + + if [ ! -s $dir_scripts/requirements.txt ] || [[ $(diff $dir_sample/requirements.txt $dir_scripts/requirements.txt) ]]; then + cp -f $dir_sample/requirements.txt $dir_scripts/requirements.txt + cd $dir_scripts + pip3 install -r $dir_scripts/requirements.txt + fi + + [ ! -d $dir_scripts/node_modules ] && npm_install_2 $dir_scripts + + cd $dir_current +} + +## 导入配置文件,检测平台,创建软连接,识别命令,修复配置文件 +detect_termux +detect_macos +define_cmd +fix_config +import_config_no_check \ No newline at end of file diff --git a/shell/task.sh b/shell/task.sh index 1c69fac4..f9448348 100755 --- a/shell/task.sh +++ b/shell/task.sh @@ -1,9 +1,5 @@ #!/usr/bin/env bash -## 路径 -dir_shell=$(dirname $(readlink -f "$0")) -dir_root=$(cd $dir_shell; pwd) - ## 导入通用变量与函数 . $dir_shell/share.sh @@ -13,9 +9,6 @@ combine_sub () { local combined_all="" local tmp1 tmp2 for ((i=1; i<=$user_sum; i++)); do - for num in $TempBlockCookie; do - [[ $i -eq $num ]] && continue 2 - done local tmp1=$what_combine$i local tmp2=${!tmp1} combined_all="$combined_all&$tmp2" @@ -65,7 +58,7 @@ gen_array_scripts () { local i="-1" cd $dir_scripts for file in $(ls); do - if [ -f $file ] && [[ $file == *.js && $file != sendNotify.js && $file != JD_extra_cookie.js ]]; then + if [ -f $file ] && [[ $file == *.js && $file != sendNotify.js ]]; then let i++ array_scripts[i]=$(echo "$file" | perl -pe "s|$dir_scripts/||g") array_scripts_name[i]=$(grep "new Env" $file | awk -F "'|\"" '{print $2}' | head -1) @@ -101,7 +94,7 @@ run_normal () { local p1=$1 cd $dir_scripts if [ -f $p1 ]; then - import_config_and_check "$p1" + import_config_and_check define_program "$p1" combine_all [[ $# -eq 1 ]] && random_delay @@ -121,15 +114,12 @@ run_concurrent () { local p1=$1 cd $dir_scripts if [ -f $p1 ]; then - import_config_and_check "$p1" + import_config_and_check define_program make_dir $dir_log/$p1 log_time=$(date "+%Y-%m-%d-%H-%M-%S.%N") echo -e "\n各账号间已经在后台开始并发执行,前台不输入日志,日志直接写入文件中。\n" for ((user_num=1; user_num<=$user_sum; user_num++)); do - for num in ${TempBlockCookie}; do - [[ $user_num -eq $num ]] && continue 2 - done combine_one $user_num log_path="$dir_log/$p1/${log_time}_${user_num}.log" $which_program $p1 &>$log_path & diff --git a/shell/update.sh b/shell/update.sh index ec0463eb..dcca559e 100755 --- a/shell/update.sh +++ b/shell/update.sh @@ -1,24 +1,11 @@ #!/usr/bin/env bash -## 文件路径、脚本网址 -dir_shell=$(dirname $(readlink -f "$0")) -dir_root=$( - cd $dir_shell - cd .. - pwd -) send_mark=$dir_shell/send_mark # 导入通用变量与函数 . $dir_shell/share.sh . $dir_shell/api.sh -## 导入配置文件,检测平台,创建软连接,识别命令,修复配置文件 -detect_termux -detect_macos -define_cmd -fix_config -import_config_no_check "update" get_token ## 重置仓库remote url,docker专用,$1:要重置的目录,$2:要重置为的网址 @@ -78,14 +65,6 @@ diff_cron() { fi } -## 更新docker-entrypoint,docker专用 -update_docker_entrypoint() { - if [[ $QL_DIR ]] && [[ $(diff $dir_root/docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh) ]]; then - cp -f $dir_root/docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh - chmod 777 /usr/local/bin/docker-entrypoint.sh - fi -} - ## 检测配置文件版本 detect_config_version() { ## 识别出两个文件的版本号 @@ -113,61 +92,6 @@ detect_config_version() { fi } -## npm install 子程序,判断是否为安卓,判断是否安装有yarn -npm_install_sub() { - if [ $is_termux -eq 1 ]; then - npm install --production --no-save --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links --no-save - elif ! type yarn >/dev/null 2>&1; then - npm install --production --no-save --registry=https://registry.npm.taobao.org || npm install --production --no-save - else - echo -e "检测到本机安装了 yarn,使用 yarn 替代 npm...\n" - yarn install --production --network-timeout 1000000000 --registry=https://registry.npm.taobao.org || yarn install --production --network-timeout 1000000000 - fi -} - -## npm install,$1:package.json文件所在路径 -npm_install_1() { - local dir_current=$(pwd) - local dir_work=$1 - - cd $dir_work - echo -e "运行 npm install...\n" - npm_install_sub - [[ $? -ne 0 ]] && echo -e "\nnpm install 运行不成功,请进入 $dir_work 目录后手动运行 npm install...\n" - cd $dir_current -} - -npm_install_2() { - local dir_current=$(pwd) - local dir_work=$1 - - cd $dir_work - echo -e "检测到 $dir_work 的依赖包有变化,运行 npm install...\n" - npm_install_sub - [[ $? -ne 0 ]] && echo -e "\n安装 $dir_work 的依赖包运行不成功,再次尝试一遍...\n" - npm_install_1 $dir_work - cd $dir_current -} - -## 比对两个文件,$1比$2新时,将$1复制为$2 -diff_and_copy() { - local copy_source=$1 - local copy_to=$2 - if [ ! -s $copy_to ] || [[ $(diff $copy_source $copy_to) ]]; then - cp -f $copy_source $copy_to - fi -} - -## 更新依赖 -update_depend() { - if [ ! -s $dir_scripts/package.json ] || [[ $(diff $dir_sample/package.json $dir_scripts/package.json) ]]; then - cp -f $dir_sample/package.json $dir_scripts/package.json - npm_install_2 $dir_scripts - fi - - [ ! -d $dir_scripts/node_modules ] && npm_install_2 $dir_scripts -} - ## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效 output_list_add_drop() { local list=$1 @@ -299,11 +223,12 @@ run_extra_shell() { ## 脚本用法 usage() { echo -e "本脚本用法:" - echo -e "2. $cmd_update update # 只更新qinglong,不会运行extra.sh" - echo -e "2. $cmd_update rebuild # 重新编译qinglong,不会运行extra.sh" - echo -e "3. $cmd_update raw # 只更新raw文件,不会运行extra.sh" - echo -e "4. $cmd_update repo # 更新所有设置的REPO,不会运行extra.sh" - echo -e "5. $cmd_update # 指定scripts脚本目录下某个文件夹名称,只更新这个文件夹中的脚本,当该文件夹已经存在并且是git仓库才可使用此命令,不会运行extra.sh" + echo -e "1. $cmd_update update # 更新青龙,并且运行extra.sh" + echo -e "2. $cmd_update rebuild # 重新编译青龙,不会运行extra.sh" + echo -e "3. $cmd_update raw # 更新单个文件脚本" + echo -e "4. $cmd_update repo # 更新仓库的脚本" + echo -e "5. $cmd_update rmlog # 删除旧日志" + echo -e "6. $cmd_update code # 获取互助码" } ## 更新qinglong @@ -312,11 +237,9 @@ update_qinglong() { git_pull_scripts $dir_root if [[ $exit_status -eq 0 ]]; then echo -e "\n更新$dir_root成功...\n" - make_dir $dir_config cp -f $file_config_sample $dir_config/config.sample.sh - update_docker_entrypoint - update_depend detect_config_version + update_depend else echo -e "\n更新$dir_root失败,请检查原因...\n" fi @@ -409,6 +332,9 @@ main() { rmlog) source $dir_shell/rmlog.sh "$p2" | tee $log_path ;; + code) + source $dir_shell/code.sh + ;; *) echo -e "命令输入错误...\n" usage diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx index 005f1c4f..7e97522c 100644 --- a/src/pages/crontab/index.tsx +++ b/src/pages/crontab/index.tsx @@ -302,24 +302,22 @@ const Crontab = () => { }> 编辑 + + ) : ( + + ) + } + > + {record.status === CrontabStatus.disabled ? '启用' : '禁用'} + {record.isSystem !== 1 && ( - <> - - ) : ( - - ) - } - > - {record.status === CrontabStatus.disabled ? '启用' : '禁用'} - - }> - 删除 - - + }> + 删除 + )} } @@ -436,6 +434,9 @@ const Crontab = () => { marginLeft, }, }} + style={{ + height: '100vh', + }} >