Skip to content

Commit

Permalink
开源自架云端源代码
Browse files Browse the repository at this point in the history
  • Loading branch information
easychen committed May 24, 2022
1 parent 68cfe51 commit 8e7d21f
Show file tree
Hide file tree
Showing 20 changed files with 4,081 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ docker run -e API_KEY=YouRAPiK1 -e TZ=Asia/Chongqing -e ERROR_IMAGE=NORMAL -p 80
```
* 特别提醒:`/data`挂载的目录需要写权限
* 特别提醒2:此镜像为x86架构,暂时未提供arm架构镜像

* 特别提醒3:docker目录下的代码采用GPLV3授权,请在协议下使用

## 什么是「Check酱」

Expand Down
1 change: 1 addition & 0 deletions build.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"20220524211519"
Binary file renamed ckc.20220524175957.zip → ckc.20220524211519.zip
Binary file not shown.
1 change: 1 addition & 0 deletions docker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data
22 changes: 22 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:16-alpine3.15

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN sed -i 's/dl-4.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk add --no-cache chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
ENV CHROMIUM_PATH /usr/bin/chromium-browser
COPY SourceHanSans.ttf /usr/share/fonts/TTF/SourceHanSans.ttf

# 测试时注释掉下一行
COPY api /api

COPY init.sh /init.sh
RUN chmod +x /init.sh
EXPOSE 80

RUN echo '* * * * * /usr/local/bin/node /api/cron.js > /data/cron.txt' > /etc/crontabs/root


ENTRYPOINT ["/bin/sh", "/init.sh"]
# ENTRYPOINT ["crond", "-l 2", "-f"]
Binary file added docker/SourceHanSans.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions docker/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
117 changes: 117 additions & 0 deletions docker/api/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const { monitor_auto, send_notify, get_data_dir, get_cookies, to_time_string, logstart, logit } = require("./func");
const express = require('express');
const path = require('path');
const fs = require('fs');
const ip = require('ip');
const app = express();

const cors = require('cors');
app.use(cors());

var multer = require('multer');
var forms = multer();
const bodyParser = require('body-parser')
app.use(bodyParser.json());
app.use(forms.array());
app.use(bodyParser.urlencoded({ extended: true }));

const image_dir = get_data_dir()+'/image';
if( !fs.existsSync(image_dir )) fs.mkdirSync(image_dir);

app.use('/image', express.static(image_dir));


function checkApiKey (req, res, next) {

if( process.env.API_KEY && process.env.API_KEY != ( req.query.key||req.body.key ))
return res.json({"code":403,"message":"错误的API KEY"});

next();
}


app.all(`/`, checkApiKey , (req, res) => {
let data_write_access = true;
try {
fs.accessSync(get_data_dir(),fs.constants.W_OK);
} catch (error) {
data_write_access = false;
}

// res.json({"code":0,"message":"it works","version":"1.0","ip":ip.address(),data_write_access});
// 不再显示IP,以免误导
res.json({"code":0,"message":"it works","version":"1.0",data_write_access});
});

app.post(`/checks/upload`, checkApiKey , (req, res) => {
const data = { checks: JSON.parse(req.body.checks)||[], cookies: JSON.parse(req.body.cookies) ||{} };
const data_file = get_data_dir()+'/data.json';
try {
let cloud_checks = [];
if( fs.existsSync( data_file ) )
{
// 如果存在旧数据
const old_data = JSON.parse(fs.readFileSync( data_file, 'utf8' ));

if( old_data )
{
cloud_checks = old_data.checks.filter( item => item.is_cloud_task == 1 );

for( const check of cloud_checks )
{
const the_idx = data.checks.findIndex(item => item.id == check.id);

// console.log(to_time_string( data.checks[the_idx]['last_time'] ) + '~' + to_time_string( check.last_time ));

if( the_idx >= 0 && data.checks[the_idx]['last_time'] < check.last_time )
{
console.log("new", check.title, check.last_time);
data.checks[the_idx]['last_content'] = check['last_content'];
data.checks[the_idx]['last_time'] = check['last_time'];
}
}
cloud_checks = data.checks.filter( item => item.is_cloud_task == 1 );
// console.log( cloud_checks );

}
// if( old_data ) cloud_checks = old_data.checks.filter( item => item.is_cloud_task == 1 );
}

fs.writeFileSync( data_file , JSON.stringify(data) );
if( fs.existsSync( data_file ) )
res.json({"code":0,"message":"设置已同步到自架服务",cloud_checks});
else
res.json({"code":501,"message":"设置保存失败"});
} catch (error) {
res.json({"code":500,"message":error});
}

});

app.post(`/monitor`, checkApiKey , async (req, res) => {
const item = JSON.parse(req.body.item);
// cookie改为从请求获取,以确保最新
const cookies = req.body?.cookies ? JSON.parse(req.body.cookies) : get_cookies();
if( !item ) return res.json({"code":500,"message":"item格式不正确"});
const ret = await monitor_auto( item, cookies );
console.log( ret );
return res.json(ret);

});

app.all(`/log`, checkApiKey , (req, res) => {
const log_file = get_data_dir()+'/log.txt';
const log = fs.existsSync( log_file ) ? fs.readFileSync( log_file, 'utf8' ): "";
res.json({"code":0,"log":log});
});

// Error handler
app.use(function (err, req, res, next) {
console.error(err);
res.status(500).send('Internal Serverless Error');
});

app.listen(80, () => {
console.log(`Server start on http://localhost`);
});

162 changes: 162 additions & 0 deletions docker/api/cron.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const fs = require("fs");
const dayjs = require("dayjs");
const { monitor_auto, send_notify, get_data_dir, cron_check, logstart, logit } = require("./func");

const data_file = get_data_dir() + 'data.json';
const content = fs.readFileSync( data_file );
const json_data = JSON.parse( content );

const to_checks = json_data.checks.filter( item => parseInt(item.enabled) === 1 && parseInt(item.is_cloud_task||0) >= 1 );

run = async () =>{
logstart();
logit("开始载入任务");
for( const item of to_checks )
{
let do_now = false;
if( parseInt( item.interval ) < 1 ) item.interval = 10;

// 判断是否应该监测
if( item.last_time )
{
if( item.interval > 0 )
{
if(dayjs(item.last_time).add(item.interval,'minutes').isBefore(dayjs())) do_now = true;
}
}else
{
do_now = true;
}

// 处理cron逻辑
if( item.cron && !cron_check(item.cron) )
{
do_now = false;
console.log("监测时间监测,跳过"+item.cron);
}

// 处理retry
if( parseInt(item.retry) < 1 ) item.retry = 10;
if( parseInt(item.retry_times) > parseInt(item.retry) )
{
do_now = false;
logit("重试次数超过,跳过"+item.title);
}
logit( item.title + "...条件检查 " +do_now );
if( do_now )
{
// 返回状态,默认为成功
// 0:未检测,1:成功但没有变动,2:成功且有异动
let check_status = 0;
let check_content = "";

logit("checking..."+item.title, dayjs().format('YYYY-MM-DD HH:mm:ss'));

const ret = await monitor_auto( item, json_data.cookies );
if( ret && ret.status )
{
check_content = ret.value;
check_status = 1;

}
else
check_status = -1;

logit( ret );

if( check_status < 0 )
{
// 失败
// 重试流程
const retry_times = parseInt( item.retry_times||0 );
if( retry_times >= item.retry )
{
// 发送通知
await send_notify( '监测点['+item.title+']多次重试失败', "已暂停执行,请检查登录状态或页面结构变动\r\n\r\n[点此查看]("+item.url+")" , item.sendkey);
}
check_update_field( item.id, 'retry_times', retry_times+1, json_data );

}else
{
// 成功分支
const last_content = item.last_content;

// 先更新再发通知,避免发送操作中断
check_update_field( item.id, 'last_content', check_content, json_data );
check_update_field( item.id, 'last_time', Date.now(), json_data );

// 重试计数清理
check_update_field( item.id, 'retry_times', 0, json_data);

let can_send_notice = true;

if( item.when == 'change' )
{
if( check_content.trim() == last_content.trim() )can_send_notice = false;
}else
{
if( item.compare_type == 'regex' )
{
if( item.regex && !check_content.match( new RegExp(item.regex, 'i') ) )
{
can_send_notice = false;
logit( '通知正则不匹配,不发送异动通知' );
}
}

if( item.compare_type == 'op' )
{
let the_value = item.compare_value;

if( item.compare_value == '*请求返回状态码*' ) the_value = item.code;

if( item.compare_value == '*上次监测返回值*' ) the_value = last_content;


if( item.compare_op == 'ne' && !(check_content != the_value)) can_send_notice = false;

if( item.compare_op == 'eq' && !(check_content == the_value)) can_send_notice = false;

if( item.compare_op == 'gt' && !(parseFloat(check_content) > parseFloat(the_value)||0)) can_send_notice = false;

if( item.compare_op == 'gte' && !(parseFloat(check_content) >= parseFloat(the_value)||0)) can_send_notice = false;

if( item.compare_op == 'lt' && !(parseFloat(check_content) < parseFloat(the_value)||0)) can_send_notice = false;

if( item.compare_op == 'lte' && !(parseFloat(check_content) <= parseFloat(the_value)||0)) can_send_notice = false;

console.log("op:",check_content,item.compare_op,the_value,can_send_notice);

}
}



if( can_send_notice )
{
logit( '已发送通知' );

if( item.sendkey )
{
await send_notify( '监测点['+item.title+']有新的通知', check_content + (last_content ? ( '←' + last_content) : "") + "\r\n\r\n[去看看]("+item.url+")" , item.sendkey);
}
}
}
}
}

logit("全部任务处理完成");

}

run();



async function check_update_field( id, field, value, json_data )
{
const the_idx = json_data.checks.findIndex(item => item.id == id);
if( the_idx < 0 ) return false;
json_data.checks[the_idx][field] = value;
fs.writeFileSync( data_file, JSON.stringify(json_data) );
}
Loading

1 comment on commit 8e7d21f

@vercel
Copy link

@vercel vercel bot commented on 8e7d21f May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

checkchan-dist – ./

ckc.ftqq.com
checkchan-dist-git-main-morexmore.vercel.app
checkchan-dist-morexmore.vercel.app

Please sign in to comment.