diff --git a/bot.log b/bot.log new file mode 100644 index 0000000..e69de29 diff --git a/commons/__pycache__/apiUtil.cpython-37.pyc b/commons/__pycache__/apiUtil.cpython-37.pyc new file mode 100644 index 0000000..704f87c Binary files /dev/null and b/commons/__pycache__/apiUtil.cpython-37.pyc differ diff --git a/commons/__pycache__/logUtil.cpython-37.pyc b/commons/__pycache__/logUtil.cpython-37.pyc new file mode 100644 index 0000000..cc66f6b Binary files /dev/null and b/commons/__pycache__/logUtil.cpython-37.pyc differ diff --git a/commons/apiUtil.py b/commons/apiUtil.py new file mode 100644 index 0000000..121b598 --- /dev/null +++ b/commons/apiUtil.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# 负责调用平台接口 完成各项操作 +import sys +sys.path.append('..') +import requests +import json +import config.baseConfig as baseconfig +from commons.logUtil import logger + + +# 获取 pull状态新信息 +def get_pull_infor(owner, repo, index): + url = baseconfig.apiUrl + "api/v1/{}/pulls/{}.json".format(repo, index) + response = requests.get(url, headers=baseconfig.header, proxies = baseconfig.proxies) + logger.info("get_pull_infor调用:"+str(response.json())) + return response.json() + + +# 获取 添加pull评论信息 +def create_pull_comment(issue_id): + url = baseconfig.apiUrl + "api/issues/{}/journals.json".format(issue_id) + COMMENT = "注意!\n该合并请求已创建满两小时,长时间未处理可能会降低贡献的质量和贡献者积极性。\n请及时处理!" + data = json.dumps({'content': COMMENT}) + + response = requests.post(url, data=data, headers=baseconfig.header, proxies = baseconfig.proxies) + logger.info("create_pull_comment调用:"+str(response.json())) + return response.json() + + +if __name__ == '__main__': + get_pull_infor("wuxiaojun", "wuxiaojun/botreascrch", "3") diff --git a/commons/authUtil.py b/commons/authUtil.py new file mode 100644 index 0000000..94e542c --- /dev/null +++ b/commons/authUtil.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# 负责调用平台接口 完成各项操作 +import json +import sys +sys.path.append('..') +import jwt +import time +import sys + + +# 通过bot基本信息字段获取JWT 和 token +def getJWT(): + if len(sys.argv) > 1: + pem = sys.argv[1] + else: + # pem = input("Enter path of private PEM file: ") + pem = 'C:\\Users\\文学奖\\Downloads\\my-first-apps1.2023-02-05.private-key (1).pem' # (1) + + # Get the App ID + if len(sys.argv) > 2: + app_id = sys.argv[2] + else: + # app_id = input("Enter your APP ID: ") + app_id = 10009 #"CrnV5uYEmwMGu7H6osHHi-4aSyKdY99qRMw4i44Kfi4" #186702 + + # Open PEM + with open(pem, 'rb') as pem_file: + signing_key = jwt.jwk_from_pem(pem_file.read()) + #signing_key = jwt.jwk_from_pem(privateKey) + + payload = { + # Issued at time + 'iat': int(time.time()), + # JWT expiration time (10 minutes maximum) + 'exp': int(time.time()) + 600, + # GitHub App's identifier + 'iss': app_id + } + + # Create JWT + jwt_instance = jwt.JWT() + encoded_jwt = jwt_instance.encode(payload, signing_key, alg='RS256') + print(f"JWT: ", encoded_jwt) + + +if __name__ == "__main__": + print("hello") \ No newline at end of file diff --git a/commons/logUtil.py b/commons/logUtil.py new file mode 100644 index 0000000..39ebcee --- /dev/null +++ b/commons/logUtil.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +import logging +import logging.handlers + + +''' +日志工具类 +''' +class Logging: + def __init__(self): + # log文件存储路径 + self._log_filename = 'bot.log' + + ''' + %(levelno)s: 打印日志级别的数值 + %(levelname)s: 打印日志级别名称 + %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0] + %(filename)s: 打印当前执行程序名 + %(funcName)s: 打印日志的当前函数 + %(lineno)d: 打印日志的当前行号 + %(asctime)s: 打印日志的时间 + %(thread)d: 打印线程ID + %(threadName)s: 打印线程名称 + %(process)d: 打印进程ID + %(message)s: 打印日志信息 + ''' + # 日志信息输出格式 + self._formatter = logging.Formatter('%(asctime)s - %(process)d - ' + '%(pathname)s - %(levelname)s: %(message)s') + # 创建一个日志对象 + self._logger = logging.getLogger() + # 设置控制台日志的输出级别: 级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG + self._logger.setLevel(logging.INFO) # 大于info级别的日志信息都会被输出 + self.set_console_logger() + self.set_file_logger() + + def set_console_logger(self): + '''设置控制台日志输出''' + console_handler = logging.StreamHandler() + console_handler.setFormatter(self._formatter) + self._logger.addHandler(console_handler) + + def set_file_logger(self): + '''设置日志文件输出''' + formatter = logging.Formatter('%(asctime)s - %(process)d - ' + '%(pathname)s - %(levelname)s: %(message)s') + # 将输出日志信息保存到文件中 + file_handler = logging.handlers.RotatingFileHandler( + self._log_filename, maxBytes=10485760, backupCount=5, encoding="utf-8") + file_handler.setFormatter(self._formatter) + self._logger.addHandler(file_handler) + + def get_logger(self): + return self._logger + + +logger = Logging().get_logger() \ No newline at end of file diff --git a/config/__pycache__/baseConfig.cpython-37.pyc b/config/__pycache__/baseConfig.cpython-37.pyc new file mode 100644 index 0000000..42ad96b Binary files /dev/null and b/config/__pycache__/baseConfig.cpython-37.pyc differ diff --git a/config/baseConfig.py b/config/baseConfig.py new file mode 100644 index 0000000..68f241c --- /dev/null +++ b/config/baseConfig.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# mysql 数据库配置信息 +db_config = { + 'host': 'localhost', + 'user': '', + 'password': '', + 'database': 'pulldb', + 'use_unicode': True, + 'charset': 'utf8mb4' +} + + +# request 头部配置 目前token写死在头部,正常需通过 api来获取有效token +header = { + 'User-Agent': 'Mozilla/5.0', + 'Authorization': 'Bearer token_str', + 'Content-Type': 'application/json', +} + +# app配置 +host = "0.0.0.0" +port = 3090 + +# request 代理配置 +proxies = {"http": None, "https": None} + + +# 平台接口url +apiUrl = "https://www.gitlink.org.cn/" + + +# payload = { +# "action":"closed", +# "number":2, +# "pull_request":{ +# "id":3406, +# "url":"https://gitlink.org.cn/wuxiaojun/botreascrch/pulls/2", +# "number":2, +# "user":{ +# "id":42378, +# "login":"wuxiaojun", +# }, +# "created_at":"2023-03-22T21:36:54+08:00", +# }, +# "repository":{ +# "id":10203, +# "name":"botreascrch", +# } +# } diff --git a/config/botConfig.py b/config/botConfig.py new file mode 100644 index 0000000..d998832 --- /dev/null +++ b/config/botConfig.py @@ -0,0 +1,11 @@ +#!/usr/bin/python + +botId = 10009 + +clientId = "ENmlgAJo2FBsENZH0DE-xbS67bxOSyTjMPNRoi_pzXQ" + +clientSecret = "xaMJ4Xyb4s5SAthe9ZCo6zX7LrB9cG6y-vrucEmXsIk" + +pemPath = 'C:\\Users\\文学奖\\Downloads\\my-first-apps1.2023-02-05.private-key (1).pem' + +token = "1XPMnNzTSS8s3hlYvHfw0wivKHJvtQLtzCt3mDRnOqs" diff --git a/controllers/__pycache__/prController.cpython-37.pyc b/controllers/__pycache__/prController.cpython-37.pyc new file mode 100644 index 0000000..37b3e06 Binary files /dev/null and b/controllers/__pycache__/prController.cpython-37.pyc differ diff --git a/controllers/prController.py b/controllers/prController.py new file mode 100644 index 0000000..9401e4d --- /dev/null +++ b/controllers/prController.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +import sys +sys.path.append('..') +from flask import Flask, jsonify, request +from flask_cors import CORS, cross_origin +import json +import services.pullService as pullService +import datetime +from commons.logUtil import logger + +app = Flask(__name__) + + +# 测试 +@app.route('/hello', methods=['get']) +@cross_origin() +def hello(): + return jsonify({ + "code": 0, + "msg": "hello" + }) + + +# 功能:每当有一个新的PR创建/关闭时,根据状态执行存储或更新操作 +@app.route('/prwelcome', methods=['POST']) +@cross_origin() +def pr_welcome(): + try: + payload = request.json + print(payload) + logger.info("获取webhook信息成功:" + str(payload)) + # # 判断是新创建issue,还是issue的状态发生改变 + if payload["action"] == 'opened': + pull_id = payload["pull_request"]["id"] + index = payload["pull_request"]["number"] + time = payload["pull_request"]["created_at"][:19] + owner = payload["pull_request"]["user"]["login"] + repo = payload["repository"]["full_name"] + created = datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") + check_time = created + datetime.timedelta(hours=2) + values = (pull_id, index, owner, repo, created, check_time, 0) + # print(values) + pullService.insert_pull(values) + + # 关闭 更新数据库对应pr状态 (合并或者拒绝都属于关闭) + elif payload["action"] == 'closed': + index = payload["pull_request"]["number"] + owner = payload["pull_request"]["user"]["login"] + repo = payload["repository"]["full_name"] + pullService.update_pull(index, owner, repo) + + return jsonify({ + "code": 0, + "msg": "success" + }) + except Exception as e: + return jsonify({ + "code": -1, + "msg": "exception:" + str(e) + }) + + +if __name__ == '__main__': + app.run(port=3090) + diff --git a/dbResource/pulldb.sql b/dbResource/pulldb.sql new file mode 100644 index 0000000..fec4a23 --- /dev/null +++ b/dbResource/pulldb.sql @@ -0,0 +1,33 @@ +/* +Navicat MySQL Data Transfer + +Source Server : localhost +Source Server Version : 80023 +Source Host : localhost:3306 +Source Database : pulldb + +Target Server Type : MYSQL +Target Server Version : 80023 +File Encoding : 65001 + +Date: 2023-03-23 16:56:23 +*/ + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for pull +-- ---------------------------- +DROP TABLE IF EXISTS `pull`; +CREATE TABLE `pull` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `pullid` int NOT NULL COMMENT 'pr主键id', + `index` int DEFAULT NULL COMMENT 'pull下标', + `owner` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '仓库拥有者login', + `repo` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '仓库名称', + `created` datetime NOT NULL COMMENT 'pr创建时间', + `closed` datetime DEFAULT NULL COMMENT '关闭时间', + `checktime` datetime NOT NULL COMMENT 'pr预期有变化时间', + `status` int NOT NULL DEFAULT '0' COMMENT 'Pull状态,0:打开,1 关闭,2 提醒完成', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/entity/pull.py b/entity/pull.py new file mode 100644 index 0000000..89ecbc5 --- /dev/null +++ b/entity/pull.py @@ -0,0 +1,3 @@ +#!/usr/bin/python + +# 注意 number 和 id有什么区别 \ No newline at end of file diff --git a/jobs/__pycache__/tasks.cpython-37.pyc b/jobs/__pycache__/tasks.cpython-37.pyc new file mode 100644 index 0000000..8bafee8 Binary files /dev/null and b/jobs/__pycache__/tasks.cpython-37.pyc differ diff --git a/jobs/tasks.py b/jobs/tasks.py new file mode 100644 index 0000000..85761cc --- /dev/null +++ b/jobs/tasks.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +import sys +sys.path.append('..') +import datetime +from apscheduler.schedulers.blocking import BlockingScheduler + +# 定时任务处理与调度 + + +# 测试程序 +def func(): + now = datetime.datetime.now() + ts = now.strftime('%Y-%m-%d %H:%M:%S') + print('do func time :', ts) + + +# 扫描数据库,获取未完成的到期pr,执行相关操作 +def check_pr(): + # 查询数据库,获取未完成的到期pr + + # 循环遍历,调用接口查询pr状态(是否新增评论或者状态为关闭) + + # 对不活跃的pr调用接口,发表评论提醒处理 + + + return + + +# 定时执行任务 +# 参数 func 定时函数 time 间隔时间(min) +def do_timer_job(func, time): + # 创建调度器:BlockingScheduler + scheduler = BlockingScheduler() + # 添加任务,时间间隔5S + scheduler.add_job(func, 'interval', seconds=time, id='timer_job1') + scheduler.start() + + +if __name__ == '__main__': + do_timer_job(func, 1) diff --git a/main.py b/main.py new file mode 100644 index 0000000..4efd2d4 --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# 项目主函数入口 +import datetime +import time +from commons.logUtil import logger +from controllers.prController import app +from services.pullService import check_and_update_pull +from threading import Thread +import config.baseConfig as baseConfig + + +def monitoring_loop(): + while True: + check_and_update_pull() + now = datetime.datetime.now() + logger.info('执行定时任务 :' + now.strftime('%Y-%m-%d %H:%M:%S')) + time.sleep(600) + + +def run(): + monitoring_thread = Thread(target=monitoring_loop) + monitoring_thread.start() + + app.run(host=baseConfig.host, port=baseConfig.port) + + +if __name__ == '__main__': + run() + + + diff --git a/services/__pycache__/pullService.cpython-37.pyc b/services/__pycache__/pullService.cpython-37.pyc new file mode 100644 index 0000000..a74bc38 Binary files /dev/null and b/services/__pycache__/pullService.cpython-37.pyc differ diff --git a/services/pullService.py b/services/pullService.py new file mode 100644 index 0000000..abc3fed --- /dev/null +++ b/services/pullService.py @@ -0,0 +1,120 @@ +import sys + +sys.path.append('..') +import config.baseConfig as baseConfig +import pymysql +import datetime +import commons.apiUtil as api + + +# 插入一条pull表记录 +def insert_pull(values): + # 打开数据库连接 + db = pymysql.connect(**baseConfig.db_config) + # 使用 cursor() 方法创建一个游标对象 cursor + cursor = db.cursor() + + # 插入语句 + # value = (1, 2, "b", "b", now_time, now_time, 0) + ins_sql = """INSERT INTO pull(`pullid`, `index`, `owner`, `repo`, `created`, `checktime`, `status`) + VALUES (%s, %s, "%s", "%s","%s", "%s", %s)""" % values + print(ins_sql) + try: + cursor.execute(ins_sql) + db.commit() + except Exception as e: + print("ERR0R!insert pull error:") + db.rollback() + print(e) + # 关闭数据库连接 + db.close() + + +# 更新一条pull表记录 +def update_pull(index, owner, repo): + # 打开数据库连接 + db = pymysql.connect(**baseConfig.db_config) + # 使用 cursor() 方法创建一个游标对象 cursor + cursor = db.cursor() + # 更新语句 + sql_update = "UPDATE pull set `status` = 1 where `index` = '%d' AND `owner` = '%s' AND `repo` = '%s'" % ( + index, owner, repo) + try: + cursor.execute(sql_update) + db.commit() + except Exception as e: + print("ERR0R!update pull error:") + db.rollback() + print(e) + # 关闭数据库连接 + db.close() + + +# 查询并更新 +def check_and_update_pull(): + # 打开数据库连接 + db = pymysql.connect(**baseConfig.db_config) + # 使用 cursor() 方法创建一个游标对象 cursor + cursor = db.cursor() + + now_time = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S') + sql_query = "select * from pull where `status` = 0 and `checktime` < '%s'" % now_time + # print(sql_query) + + try: + # 执行SQL语句 + cursor.execute(sql_query) + # 获取所有记录列表 + results = cursor.fetchall() + print(results) + for item in results: + # 调用接口查询pr状态 + # (7, 1, 2, 'b', 'b', datetime.datetime(), None, datetime.datetime(2023, 3, 23, 11, 46, 13), 0) + # 提取 pr记录信息 + pull_id = item[0] + pull_index = item[2] + owner = item[3] + repo = item[4] + pr_infor = api.get_pull_infor(owner, repo, pull_index) + print(pr_infor) + issue_id = pr_infor["issue"]["id"]; + + # pr 状态标志位,0表示未完成,1表示已经完成 + flag = check_pr(pr_infor) + + # 数据库 pr记录 更新语句 + sql_update = "" + # 若新增评论或者状态为关闭 则修改数据库 + if flag == 1: + # pr已完成更新语句 + sql_update = "UPDATE pull set status = 1 where id = '%d'" % pull_id + # 否则对不活跃的pr调用接口,发表评论提醒处理 + + try: + cursor.execute(sql_update) + db.commit() + except Exception as e: + db.rollback() + print("ERR0R!update pull error:") + print(e) + except Exception as e: + db.rollback() + print("ERR0R!deal pull error:") + print(e) + # 关闭数据库连接 + db.close() + + +# 检查 pr状态,已完成返回1,未完成返回0 +def check_pr(pr_infor): + # pr已经关闭 + if pr_infor["status"] == "closed": + return 1 + # pr有动态 + if pr_infor["issue"]["journals_count"] != 0: + return 1 + return 0 + + +if __name__ == '__main__': + check_and_update_pull()