├── .gitignore ├── gitlab2ding ├── labFetchUser.py ├── README.md └── gitlab2dingsvr.py /.gitignore: -------------------------------------------------------------------------------- 1 | log-* 2 | config.ini 3 | -------------------------------------------------------------------------------- /gitlab2ding: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $1 == "start" ];then 3 | python3 -u gitlab2dingsvr.py > log-$(date +%y%m%d)_$(date +%H%M) 2>&1 & 4 | exit 0 5 | fi 6 | if [ $1 == "restart" ];then 7 | ps -ef | grep gitlab2dingsvr | grep -v grep | cut -c 9-15 | xargs kill 8 | python3 -u gitlab2dingsvr.py > log-$(date +%y%m%d)_$(date +%H%M) 2>&1 & 9 | exit 0 10 | fi 11 | if [ $1 == "stop" ];then 12 | ps -ef | grep gitlab2dingsvr | grep -v grep | cut -c 9-15 | xargs kill 13 | exit 0 14 | fi 15 | echo "need param.return 1" 16 | exit 1 -------------------------------------------------------------------------------- /labFetchUser.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler, HTTPServer 2 | from os import path 3 | from urllib.parse import urlparse 4 | import urllib 5 | from urllib import request,parse 6 | from time import localtime, strftime 7 | import json,configparser 8 | import sqlite3 9 | 10 | 11 | config = configparser.ConfigParser() 12 | config.read("config.ini") 13 | corpid = config["config"]["corpid"] 14 | corpsecret = config["config"]["corpsecret"] 15 | 16 | req_dingapi = request.Request("https://oapi.dingtalk.com/gettoken?corpid="+corpid+"&corpsecret="+corpsecret) 17 | data_from_dingapi = request.urlopen(req_dingapi).read() 18 | data_for_access_token = json.loads(data_from_dingapi.decode("utf-8")) 19 | access_token = data_for_access_token['access_token'] 20 | 21 | url = "https://oapi.dingtalk.com/department/list?access_token="+access_token 22 | req_dingapi = request.Request(url) 23 | data_from_dingapi = request.urlopen(req_dingapi).read() 24 | data_departments = json.loads(data_from_dingapi.decode("utf-8")) 25 | data_departments_list = data_departments["department"] 26 | 27 | sqconn = sqlite3.connect('gitlab2ding.db') 28 | sqc = sqconn.cursor() 29 | 30 | 31 | name_list = {} 32 | for department in data_departments_list: 33 | req_dingapi = request.Request("https://oapi.dingtalk.com/user/list?access_token="+access_token+"&department_id="+str(department["id"])) 34 | data_from_dingapi = request.urlopen(req_dingapi).read().decode("utf-8") 35 | data = json.loads(data_from_dingapi) 36 | data_userlist = data["userlist"] 37 | for user in data_userlist: 38 | if not user["name"] in name_list: 39 | sq_add_str="INSERT OR IGNORE INTO USERS (PHONE,NAME,DINGUID) VALUES ('"+user["mobile"]+"', '"+user["name"]+"', '"+user["userid"]+"')" 40 | sqc.execute(sq_add_str); 41 | name_list[user["name"]] = "added" 42 | 43 | sqconn.commit() 44 | sqconn.close() 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitlab钉钉通知服务 2 | 3 | ## 简介 4 | 5 | 如果你的开发团队使用gitlab作为托管仓库、开发周期管理和持续集成工具,那么肯定会希望gitlab的事件(例如issue回复、mergerequest评论、pipeline状态等)能够及时通知到相关成员,gitlab虽然有相关的提醒,但是基本遵照老外的工作惯例,使用邮件进行通知 6 | 如果你的团队同时使用钉钉作为日常的IM工具的话,本项目可以帮助你将gitlab事件通知到相关人员的钉钉上 7 | 8 | 项目使用了gitlab的webhook,gitlab将事件消息发送到本服务上,再由本服务调用钉钉开放接口将消息发送到指定个人 9 | 10 | ## 文件结构 11 | 12 | * gitlab2ding:shell快捷命令,启停和重启,需要添加执行权限 13 | * gitlab2dingsvr.py:服务主程序,接受webhook并转为钉钉消息发送,接收并处理账号绑定 14 | * labFetchUser.py:获取全体成员钉钉uid 15 | 16 | ## 配置 17 | 18 | ### config.ini 文件【必须】 19 | 20 | [config] 21 | debug = 0 22 | debuger = 0123456789 23 | agentid = 0123456789 24 | port = 30000 25 | corpid = 0123456789 26 | corpsecret = 12345qwerty 27 | gitlabtoken = 12345qwerty 28 | gitlaburl = http://gitlab.yours.com 29 | webhookurl = http://gitlab.yours.com/gitlab2dingsvr 30 | 31 | ----------- 32 | * debug : 33 | * 0 :normal 34 | * 1 :send to user and debuger 35 | * 2 :send to debuger only 36 | * debuger:debuger的dinguid 37 | * agentid:注册钉钉企业应用时提供的应用id 38 | * port:服务监听的端口,设置在gitlab服务器上的话需要更改gitlab的nginx配置进行定向 39 | * corpid和corpsecret:钉钉为企业应用开发提供的企业信息查询token,在向钉钉请求和发送数据时使用 40 | * gitlabtoken:使用gitlab api时所需要的token 41 | * gitlaburl:gitlab服务器url,不要带最后的斜杠,如`http://gitlab.yours.com` 42 | * webhookurl:运行本通知服务的地址,同样不要带最后的斜杠,如`http://gitlab.yours.com/gitlab2dingsvr` 43 | * 以上字段均不需要使用引号包围 44 | 45 | ### gitlab配置 46 | 47 | 例如,gitlab服务器的url为`http://gitlab.yours.com`,并且希望把本服务配置在`http://gitlab.yours.com/gitlab2dingsvr`,则需要在gitlab服务器上找到`/etc/gitlab/gitlab.rb`文件,并添加如下配置 48 | 49 | `nginx['custom_gitlab_server_config'] = "location /gitlab2dingsvr {\n proxy_cache off; \nproxy_pass http://127.0.0.1:30000;\n}\n"` 50 | 51 | 其中端口号30000可以自定义,但是需要与上文config中port项配置的端口号相同。修改该配置文件后需要运行`sudo gitlab-ctl reconfigure`使配置生效 52 | 53 | ## 运行 54 | 55 | 1. 配置config.ini和gitlab.rb 56 | 2. 新建一个sqlite数据库(推荐用 SQLiteStudio),文件名设为gitlab2ding.db,然后在里面建一张表,表的名字是USERS,字段配置如图,建好以后放到同个目录下面就好 57 | ![image](https://user-images.githubusercontent.com/6895745/52922595-3115d400-335d-11e9-96b9-30844450e514.png) 58 | 3. gitlab2ding添加运行权限 59 | 4. 启动服务:`./gitlab2ding start` 60 | 61 | ## 使用 62 | 63 | ### 1.关联钉钉账号 64 | 65 | 现在可以直接访问 `http://gitlab.yours.com/gitlab2dingsvr?action=linkfront` (改成你自己的webhookurl) 66 | 67 | 或者使用下方的老方法(实际上就是用html包装了一下) 68 | 69 | 复制下方链接,并根据自己实际情况修改相应字段: 70 | * mobile: 必须。你的钉钉注册手机号。 71 | * username: 必须。你在Gitlab上的**Username(点开右上角头像后第二行@开头的字段,也是你登录时所用的用户名,此项务必检查无误)** 72 | * email: 可选。你在git提交中使用的email(如果你不是代码开发者,则不需要提交这一项) 73 | 74 | `http://gitlab.yours.com/gitlab2dingsvr?action=linkuser&mobile=13012341234&username=user` 75 | 76 | `http://gitlab.yours.com/gitlab2dingsvr?action=linkuser&mobile=13012341234&username=user&email=user@mail.com` 77 | 78 | 改好以后复制到浏览器地址栏中直接访问,获得如下结果则关联成功。 79 | 80 | ``` 81 | User found. 82 | Git email added. 83 | Gitlab username added. 84 | ``` 85 | 86 | ### 2.在project中添加webhook 87 | 目前已经支持新项目自动添加webhook,但已有的项目依然需要手动配置 88 | 1. 请project负责人打开项目页面,在左侧导航栏中打开Settings - Integrations 89 | 2. 在打开的设置页面中,在第一行的URL添加地址:`http://gitlab.yours.com/gitlab2dingsvr`然后在下方的Trigger选择区域勾选所有消息类型。 90 | 3. 完成后在下方点击Add Webhook即可。 91 | 92 | ## 备注 93 | 94 | * 一些和钉钉交互的功能详情,可以查阅[钉钉开放平台的api文档](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.AFXfTL&treeId=367&articleId=107549&docType=1) -------------------------------------------------------------------------------- /gitlab2dingsvr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #--coding:utf-8-- 3 | 4 | from http.server import BaseHTTPRequestHandler, HTTPServer 5 | from os import path 6 | from urllib.parse import urlparse 7 | import urllib 8 | from urllib import request,parse 9 | from time import localtime, strftime 10 | import json,configparser,sqlite3,os 11 | 12 | config = configparser.ConfigParser() 13 | config.read("config.ini") 14 | 15 | def log(log_str): 16 | timestamp = strftime("[%d/%b/%Y %H:%M:%S]", localtime()) 17 | print(timestamp + log_str) 18 | 19 | def read_list_from_db(list_type_str): 20 | sqconn = sqlite3.connect('gitlab2ding.db') 21 | sqc = sqconn.cursor() 22 | if list_type_str == "git_members": 23 | user_list_tuple = sqc.execute("SELECT GITEMAIL,DINGUID FROM USERS") 24 | elif list_type_str == "lab_members": 25 | user_list_tuple = sqc.execute("SELECT GITLABUSERNAME,DINGUID FROM USERS") 26 | elif list_type_str == "ding_user_list": 27 | user_list_tuple = sqc.execute("SELECT PHONE,DINGUID FROM USERS WHERE GITLABUSERNAME IS NULL") 28 | db_list_dict = {} 29 | 30 | db_user = user_list_tuple.fetchone() 31 | while db_user!=None: 32 | if db_user[0]!=None: 33 | db_list_dict[db_user[0]] = db_user[1] 34 | db_user = user_list_tuple.fetchone() 35 | 36 | sqconn.close() 37 | log(list_type_str+" updated from sqlite db") 38 | return db_list_dict 39 | 40 | # some start up work 41 | 42 | try: 43 | os.system("python3 labFetchUser.py") 44 | except: 45 | log("labFetchUser fail") 46 | else: 47 | log("labFetchUser success") 48 | git_members = read_list_from_db("git_members") 49 | lab_members = read_list_from_db("lab_members") 50 | 51 | print(git_members) 52 | print(lab_members) 53 | 54 | AgentID = config["config"]["agentid"] 55 | port = int(config["config"]["port"]) 56 | debug = config["config"]["debug"] 57 | debuger = config["config"]["debuger"] 58 | # debug 0 :normal 59 | # 1 :send to user and debuger 60 | # 2 :send to debuger only 61 | corpid = config["config"]["corpid"] 62 | corpsecret = config["config"]["corpsecret"] 63 | gitlabtoken = config["config"]["gitlabtoken"] 64 | gitlaburl = config["config"]["gitlaburl"] 65 | webhookurl = config["config"]["webhookurl"] 66 | 67 | # start up finish 68 | 69 | def sqlite_linkuser_add(phone_str,value_str,key_str): 70 | sqconn = sqlite3.connect('gitlab2ding.db') 71 | sqc = sqconn.cursor() 72 | sqc.execute("SELECT PHONE FROM USERS") 73 | try: 74 | sqc.execute("UPDATE USERS set "+key_str+" = '"+value_str+"' where PHONE='"+phone_str+"'") 75 | except : 76 | log( "sqlite_linkuser_add fail:"+phone_str+";"+value_str+";"+key_str) 77 | sqconn.commit() 78 | sqconn.close() 79 | else: 80 | log( "sqlite_linkuser_add success:"+phone_str+";"+value_str+";"+key_str) 81 | sqconn.commit() 82 | sqconn.close() 83 | 84 | def labuid2username(uid_str): 85 | req_labid2username = request.Request(gitlaburl+"/api/v4/users/"+uid_str+"?private_token="+gitlabtoken) 86 | data_from_lab = request.urlopen(req_labid2username).read() 87 | data_for_username = json.loads(data_from_lab.decode("utf-8")) 88 | username = data_for_username["username"] 89 | return username 90 | 91 | def receiverSort(receiver_list): 92 | receiver_str = "" 93 | if len(receiver_list) > 0 : 94 | for receiver in receiver_list : 95 | receiver_str=receiver_str + "|" + receiver 96 | else: 97 | return "" 98 | if (receiver_str[0]=="|"): 99 | receiver_str = receiver_str[1:] 100 | if (debug == "1" ): 101 | log( "[debug]receiver_str:" + receiver_str) 102 | receiver_str = receiver_str+ "|" + debuger 103 | if (debug == "2" ): 104 | log( "[debug]receiver_str:" + receiver_str) 105 | receiver_str = debuger 106 | return receiver_str 107 | 108 | def searchAt(receiver_list,content_str): 109 | content_dict = content_str.split("@") 110 | for content in content_dict: 111 | i=0 112 | for c in content: 113 | if (ord(c)>=65 and ord(c)<=90) or (ord(c)>=97 and ord(c)<=122): 114 | i=i+1 115 | else: 116 | break 117 | 118 | for key in lab_members.keys(): 119 | if (i>0 and content[:i]==key): 120 | receiver_list.append(lab_members[key])#to at 121 | log("at "+key+" get.") 122 | break 123 | 124 | return receiver_list 125 | 126 | class gitlab2dingsvr_RequestHandler(BaseHTTPRequestHandler): 127 | # GET 128 | 129 | def do_POST(self): 130 | timestamp = strftime("[%d/%b/%Y %H:%M:%S]", localtime()) 131 | querypath = urlparse(self.path) 132 | filepath, query = querypath.path, querypath.query 133 | data_b = self.rfile.read(int(self.headers['content-length'])) 134 | self.send_response(200) 135 | self.send_header('Content-type',"None") 136 | self.end_headers() 137 | 138 | data_s = data_b.decode("utf-8") 139 | data_json = json.loads(data_s) 140 | receiver_dingUid = [] 141 | send_valid=0; 142 | 143 | if "event_name" in data_json: 144 | ''' 145 | ■ 146 | ■ 147 | ■■■ ■ ■ ■■■■■ ■■■ 148 | ■ ■ ■ ■ ■ ■ ■■ 149 | ■ ■ ■ ■ ■ ■ 150 | ■■■■ ■ ■ ■ ■ ■ 151 | ■ ■ ■ ■■ ■ ■ ■■ 152 | ■■■■■ ■■■ ■ ■■■ ■■■ 153 | 154 | ■ ■ ■ 155 | ■ ■ ■ 156 | ■ ■ ■ 157 | ■ ■ ■ ■■■ ■■■■ ■■■■ ■■■ ■■■ ■ ■ 158 | ■ ■■ ■ ■ ■■ ■ ■ ■■ ■■ ■ ■■ ■ ■■ ■ ■ 159 | ■■■ ■ ■■■■■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■ 160 | ■■ ■■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■ 161 | ■■ ■ ■ ■■ ■ ■ ■ ■ ■ ■■ ■ ■■ ■ ■ 162 | ■ ■ ■■■ ■■■■ ■ ■ ■■■ ■■■ ■ ■ 163 | ''' 164 | if data_json['event_name']=="project_create": 165 | new_project_id_str = str(data_json["project_id"]) 166 | log( "new project:"+data_json["path_with_namespace"]+"(id:"+new_project_id_str+"), adding webhook") 167 | addHook_json_str = ( 168 | "{\"id\":"+new_project_id_str+",\"url\": \""+webhookurl+"\" ," 169 | "\"push_events\" : 1 ," 170 | "\"issues_events\" : 1 ," 171 | "\"confidential_issues_events\" : 1, " 172 | "\"merge_requests_events\" : 1, " 173 | "\"tag_push_events\" : 1, " 174 | "\"note_events\" : 1, " 175 | "\"confidential_note_events\" : 1," 176 | "\"job_events\" : 1, " 177 | "\"pipeline_events\" : 1, " 178 | "\"wiki_page_events\" : 1, " 179 | "\"enable_ssl_verification\" : 1 }" 180 | ) 181 | 182 | addHook_json_str_b = addHook_json_str.encode('utf-8') 183 | url = gitlaburl+"/api/v4/projects/"+new_project_id_str+"/hooks" 184 | headersAddhook = {"Private-Token": gitlabtoken ,"Content-Type":"application/json"} 185 | req_addhook = request.Request(url, data=addHook_json_str_b,headers=headersAddhook, method="POST") 186 | data_from_addhook_req = request.urlopen(req_addhook).read() 187 | log( data_from_addhook_req.decode('utf_8')) 188 | 189 | elif "object_kind" in data_json: 190 | ''' 191 | ■ 192 | ■■ ■ ■■ 193 | ■■ ■ ■■ 194 | ■ 195 | ■ ■■■ ■ ■ ■■■ ■■■ ■ ■ ■ ■■■ ■■■ 196 | ■■ ■■ ■ ■■ ■■ ■ ■ ■ ■ ■■ ■■ ■ ■ 197 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 198 | ■ ■ ■ ■ ■ ■■■■■ ■ ■ ■ ■ ■■■■■ 199 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 200 | ■■ ■ ■ ■■ ■ ■■ ■ ■ ■ ■ ■ ■■ ■ 201 | ■ ■■■ ■ ■ ■■■ ■■■ ■ ■ ■ ■ ■■■ 202 | ■ ■ 203 | ■ ■ 204 | ■ ■ 205 | pipeline object 206 | to: commit(according to commit email) 207 | ''' 208 | 209 | if (data_json['object_kind'] == "pipeline"): 210 | send_valid = 1 211 | log( "pipeline trigger received") 212 | if data_json['commit']['author']['email'] in git_members: 213 | receiver_dingUid.append(git_members[data_json['commit']['author']['email']]) 214 | log( "committer email:" + data_json['commit']['author']['email'] + " found") 215 | status = data_json['object_attributes']['status'] 216 | commit_msg = data_json['commit']['message'] 217 | object_id = str(data_json['object_attributes']['id']) 218 | log(data_json["project"]["web_url"]+"/pipelines/"+object_id) 219 | project_name = data_json['project']['name'] 220 | 221 | if len(receiver_dingUid)==0 : 222 | send_valid = 0 223 | else: 224 | receiver_dingUid_str = receiverSort(receiver_dingUid) 225 | msg2dingapi_json_str=( 226 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 227 | "\"msgtype\": \"markdown\"," 228 | "\"markdown\": {\"title\": \"最近提交的CI状态变化\"," 229 | "\"text\": \"## 最近提交的CI状态变化为"+status+" \\n" 230 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 231 | "### 提交备注:"+commit_msg.replace("\"","\\\"")+" \\n" 232 | "[点击查看]("+data_json["project"]["web_url"]+"/pipelines/"+object_id+")"+timestamp+"\"} }" 233 | ) 234 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 235 | 236 | ''' 237 | ■■ 238 | ■■ 239 | 240 | ■ ■■ ■■ ■ ■ ■■■ 241 | ■ ■ ■ ■ ■ ■ ■ ■ ■ 242 | ■ ■ ■ ■ ■ ■ ■ 243 | ■ ■■ ■■ ■ ■ ■■■■■ 244 | ■ ■ ■ ■ ■ ■ 245 | ■ ■ ■ ■ ■ ■■ ■■ ■■ ■ 246 | ■ ■■ ■■ ■■■ ■ ■■■ 247 | issue object: create, at, edit 248 | msg to: author, at, assignee(according to lab username) 249 | ''' 250 | elif (data_json['object_kind'] == "issue"): 251 | send_valid=1; 252 | log( "issue trigger received") 253 | if("assignees" in data_json): 254 | if("username" in data_json["assignees"][0]): 255 | if(data_json["assignees"][0]["username"] in lab_members): 256 | receiver_dingUid.append(lab_members[data_json["assignees"][0]["username"]])#to assignee 257 | log("assignee "+data_json["assignees"][0]["username"]+" get.") 258 | issue_url = data_json["object_attributes"]["url"] 259 | log(issue_url) 260 | issue_title = data_json["object_attributes"]["title"] 261 | issue_id = str(data_json["object_attributes"]["iid"]) 262 | issue_content = data_json['object_attributes']['description'] 263 | if issue_content != None: 264 | receiver_dingUid = searchAt(receiver_dingUid,issue_content) 265 | project_name = data_json['project']['name'] 266 | 267 | 268 | if len(receiver_dingUid)==0 : 269 | send_valid = 0 270 | else: 271 | receiver_dingUid_str = receiverSort(receiver_dingUid) 272 | msg2dingapi_json_str=( 273 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 274 | "\"msgtype\": \"markdown\"," 275 | "\"markdown\": {\"title\": \"issue#"+issue_id+"通知\"," 276 | "\"text\": \"## 和你相关的issue#"+issue_id+"通知 \\n" 277 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 278 | "### 标题:"+issue_title.replace("\"","\\\"")+" \\n" 279 | "[点击查看]("+issue_url+")"+timestamp+"\"} }" 280 | ) 281 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 282 | 283 | ''' 284 | 285 | ■■ ■ ■■■■■ 286 | ■■■ ■■ ■ ■■ 287 | ■ ■ ■■ ■ ■ 288 | ■ ■■ ■ ■ ■■■ ■ ■■ ■■ ■ ■■■ ■ ■ ■■■ ■■■■ 289 | ■ ■■ ■ ■ ■ ■ ■■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■■ 290 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■■ ■ ■ ■ ■ 291 | ■ ■■ ■ ■ ■■■■■ ■ ■ ■ ■■■■■ ■ ■ ■■■■■ ■ ■ 292 | ■ ■■■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 293 | ■ ■■■ ■ ■■ ■ ■ ■■ ■■ ■■ ■ ■ ■■ ■■ ■ ■■ ■■ 294 | ■ ■ ■ ■■■ ■ ■■■ ■ ■■■ ■ ■■ ■■■ ■■■ ■ 295 | ■ ■ 296 | ■ ■ ■ 297 | ■■■ ■ 298 | 299 | merge request object: create, at, edit 300 | msg to: author, at, assignee(according to lab username) 301 | ''' 302 | elif (data_json['object_kind'] == "merge_request"): 303 | send_valid=1; 304 | log( data_json['object_kind']+" trigger received") 305 | if("assignee" in data_json): 306 | if("username" in data_json["assignee"]): 307 | if(data_json["assignee"]["username"] in lab_members): 308 | receiver_dingUid.append(lab_members[data_json["assignee"]["username"]])#to assignee 309 | log("assignee "+data_json["assignee"]["username"]+" get.") 310 | object_url = data_json["object_attributes"]["url"] 311 | log(object_url) 312 | merge_req_title = data_json["object_attributes"]["title"] 313 | merge_req_id = str(data_json["object_attributes"]["iid"]) 314 | 315 | merge_req_content = data_json['object_attributes']['description'] 316 | if merge_req_content != None: 317 | receiver_dingUid = searchAt(receiver_dingUid,merge_req_content) 318 | project_name = data_json['project']['name'] 319 | 320 | 321 | if len(receiver_dingUid)==0 : 322 | send_valid = 0 323 | else: 324 | receiver_dingUid_str = receiverSort(receiver_dingUid) 325 | msg2dingapi_json_str=( 326 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 327 | "\"msgtype\": \"markdown\"," 328 | "\"markdown\": {\"title\": \"MergeRequest#"+merge_req_id+"通知\"," 329 | "\"text\": \"## 和你相关的MergeRequest#"+merge_req_id+"通知 \\n" 330 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 331 | "### 标题:"+merge_req_title.replace("\"","\\\"")+" \\n" 332 | "[点击查看]("+object_url+")"+timestamp+"\"} }" 333 | ) 334 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 335 | 336 | ''' 337 | ■■ 338 | ■■ ■ 339 | ■ 340 | ■ ■■ ■■ ■ ■ ■■■ ■■ ■■■■ ■ ■■ ■■■ ■ ■■ ■■■ ■■■ ■ ■■■ ■■■ 341 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■ ■■ ■■ ■■ ■■ ■■ ■■ ■ ■ ■■ ■■ ■ 342 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 343 | ■ ■■ ■■ ■ ■ ■■■■■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■■■ ■ ■ ■ 344 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 345 | ■ ■ ■ ■ ■ ■■ ■■ ■■ ■ ■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■■ ■ ■ ■ ■ 346 | ■ ■■ ■■ ■■■ ■ ■■■ ■■ ■■■■ ■ ■ ■ ■ ■ ■ ■■■ ■ ■ ■ 347 | 348 | issue comment object: new comment, at, edit 349 | msg to: comment author, at and assignee 350 | ''' 351 | elif (data_json['object_kind'] == "note") and (data_json['object_attributes']['noteable_type'] == "Issue") : 352 | send_valid=1; 353 | log( data_json['object_attributes']['noteable_type'] +" comment trigger received") 354 | 355 | comment_url = data_json["object_attributes"]["url"] 356 | log(comment_url) 357 | comment_content = data_json['object_attributes']['note'] 358 | if comment_content != None: 359 | receiver_dingUid = searchAt(receiver_dingUid,comment_content) 360 | 361 | # content above are same in object kind NOTE (comment) 362 | 363 | issue_author_id = str(data_json["issue"]["author_id"]) 364 | issue_author = labuid2username(issue_author_id) 365 | if(issue_author in lab_members): 366 | receiver_dingUid.append(lab_members[issue_author]) 367 | log("issue author "+issue_author+" get.") 368 | 369 | if data_json["issue"]["assignee_id"] != None : 370 | issue_assignee_id = str(data_json["issue"]["assignee_id"]) 371 | issue_assignee = labuid2username(issue_assignee_id) 372 | if(issue_assignee in lab_members): 373 | receiver_dingUid.append(lab_members[issue_assignee]) 374 | log("issue assignee "+issue_assignee+" get.") 375 | issue_id = str(data_json["issue"]["iid"]) 376 | issue_title = str(data_json["issue"]["title"]) 377 | 378 | project_name = data_json['project']['name'] 379 | 380 | if len(receiver_dingUid)==0 : 381 | send_valid = 0 382 | else: 383 | receiver_dingUid_str = receiverSort(receiver_dingUid) 384 | msg2dingapi_json_str=( 385 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 386 | "\"msgtype\": \"markdown\"," 387 | "\"markdown\": {\"title\": \"issue#"+issue_id+"评论通知\"," 388 | "\"text\": \"## 和你相关的issue#"+issue_id+"评论 \\n" 389 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 390 | "### 标题:"+issue_title.replace("\"","\\\"")+" \\n" 391 | "[点击查看]("+comment_url+")"+timestamp+"\"} }" 392 | ) 393 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 394 | ''' 395 | 396 | ■■ 397 | ■■ ■ ■ 398 | ■ ■ 399 | ■■ ■■■■ ■ ■■ ■■■ ■ ■■ ■■■ ■ ■■■■ ■■ ■■■■ ■ ■■ ■■■ ■ ■■ ■■■ ■■■ ■ ■■■ ■■■■ 400 | ■ ■ ■ ■■ ■■ ■■ ■■ ■■ ■■ ■■ ■ ■ ■ ■ ■ ■■ ■■ ■■ ■■ ■■ ■■ ■■ ■ ■ ■■ ■■ ■ 401 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 402 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■■■ ■ ■ ■ 403 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 404 | ■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■■ ■ ■ ■ ■ 405 | ■■ ■■■■ ■ ■ ■ ■ ■ ■ ■ ■■ ■■ ■■■■ ■ ■ ■ ■ ■ ■ ■■■ ■ ■ ■■ 406 | 407 | commit comment object: new comment, at, edit 408 | msg to: comment author, at 409 | ''' 410 | elif (data_json['object_kind'] == "note") and (data_json['object_attributes']['noteable_type'] == "Commit") : 411 | send_valid=1; 412 | log( data_json['object_attributes']['noteable_type'] +" comment trigger received") 413 | 414 | comment_url = data_json["object_attributes"]["url"] 415 | log(comment_url) 416 | comment_content = data_json['object_attributes']['note'] 417 | if comment_content != None: 418 | receiver_dingUid = searchAt(receiver_dingUid,comment_content) 419 | 420 | # content above are same in object kind NOTE (comment) 421 | 422 | commit_author_email = data_json["commit"]["author"]["email"] 423 | if(commit_author_email in git_members): 424 | receiver_dingUid.append(git_members[commit_author_email]) 425 | log("commit author "+commit_author_email+" get.") 426 | 427 | commit_msg = data_json['commit']['message'] 428 | project_name = data_json['project']['name'] 429 | 430 | if len(receiver_dingUid)==0 : 431 | send_valid = 0 432 | else: 433 | receiver_dingUid_str = receiverSort(receiver_dingUid) 434 | msg2dingapi_json_str=( 435 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 436 | "\"msgtype\": \"markdown\"," 437 | "\"markdown\": {\"title\": \"commit评论通知\"," 438 | "\"text\": \"## 和你相关的commit评论 \\n" 439 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 440 | "### 提交备注:"+commit_msg.replace("\"","\\\"")+" \\n" 441 | "[点击查看]("+comment_url+")"+timestamp+"\"} }" 442 | ) 443 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 444 | 445 | ''' 446 | 447 | 448 | ■■ ■ ■■■■■ 449 | ■■■ ■■ ■ ■■ 450 | ■ ■ ■■ ■ ■ 451 | ■ ■■ ■ ■ ■■■ ■ ■■ ■■ ■ ■■■ ■ ■ ■■■ ■■■■ 452 | ■ ■■ ■ ■ ■ ■ ■■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■■ 453 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■■ ■ ■ ■ ■ 454 | ■ ■■ ■ ■ ■■■■■ ■ ■ ■ ■■■■■ ■ ■ ■■■■■ ■ ■ 455 | ■ ■■■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 456 | ■ ■■■ ■ ■■ ■ ■ ■■ ■■ ■■ ■ ■ ■■ ■■ ■ ■■ ■■ 457 | ■ ■ ■ ■■■ ■ ■■■ ■ ■■■ ■ ■■ ■■■ ■■■ ■ 458 | ■ ■ 459 | ■ ■ ■ 460 | ■■■ ■ 461 | 462 | ■ 463 | ■ 464 | ■■ ■■■■ ■ ■■ ■■■ ■ ■■ ■■■ ■■■ ■ ■■■ ■■■■ 465 | ■ ■ ■ ■■ ■■ ■■ ■■ ■■ ■■ ■■ ■ ■ ■■ ■■ ■ 466 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 467 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■■■■■ ■ ■ ■ 468 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 469 | ■ ■ ■■ ■ ■ ■ ■ ■ ■ ■ ■■ ■ ■ ■ ■ 470 | ■■ ■■■■ ■ ■ ■ ■ ■ ■ ■■■ ■ ■ ■■ 471 | 472 | merge request comment object: new comment, at, edit 473 | msg to: comment author, at, merge assignee 474 | ''' 475 | elif (data_json['object_kind'] == "note") and (data_json['object_attributes']['noteable_type'] == "MergeRequest") : 476 | send_valid=1; 477 | log( data_json['object_attributes']['noteable_type'] +" comment trigger received") 478 | comment_url = data_json["object_attributes"]["url"] 479 | log(comment_url) 480 | comment_content = data_json['object_attributes']['note'] 481 | if comment_content != None: 482 | receiver_dingUid = searchAt(receiver_dingUid,comment_content) 483 | 484 | # content above are same in object kind NOTE (comment) 485 | 486 | req_author_id = str(data_json["merge_request"]["author_id"]) 487 | req_author = labuid2username(req_author_id) 488 | if(req_author in lab_members): 489 | receiver_dingUid.append(lab_members[req_author]) 490 | log("MergeRequest author "+req_author+" get.") 491 | 492 | if data_json["merge_request"]["assignee_id"] != None : 493 | req_assignee_id = str(data_json["merge_request"]["assignee_id"]) 494 | req_assignee = labuid2username(req_assignee_id) 495 | if(req_assignee in lab_members): 496 | receiver_dingUid.append(lab_members[req_assignee]) 497 | log("MergeRequest assignee "+req_assignee+" get.") 498 | 499 | merge_req_title = data_json['merge_request']['title'] 500 | project_name = data_json['project']['name'] 501 | 502 | if len(receiver_dingUid)==0 : 503 | send_valid = 0 504 | else: 505 | receiver_dingUid_str = receiverSort(receiver_dingUid) 506 | msg2dingapi_json_str=( 507 | "{\"touser\": \""+receiver_dingUid_str+"\", \"agentid\": \""+AgentID+"\"," 508 | "\"msgtype\": \"markdown\"," 509 | "\"markdown\": {\"title\": \"MergeRequest通知\"," 510 | "\"text\": \"## 和你相关的MergeRequest评论 \\n" 511 | "### 项目:"+project_name.replace("\"","\\\"")+" \\n" 512 | "### 标题:"+merge_req_title.replace("\"","\\\"")+" \\n" 513 | "[点击查看]("+comment_url+")"+timestamp+"\"} }" 514 | ) 515 | msg2dingapi_b = msg2dingapi_json_str.encode('utf-8') 516 | 517 | ''' 518 | ■ 519 | ■ 520 | ■ 521 | ■ 522 | ■■ ■■■ ■ ■■■ ■■ ■ ■ ■■ ■■■ ■■ ■■ ■ 523 | ■ ■ ■ ■ ■■ ■■ ■ ■■ ■■ ■■ ■■ ■ ■ ■ ■■ 524 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 525 | ■■ ■■■■■ ■ ■ ■ ■ ■ ■ ■ ■■ ■ ■ 526 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 527 | ■ ■ ■■ ■ ■ ■ ■■ ■■ ■ ■ ■ ■ ■ ■■ ■■ 528 | ■■ ■■■ ■ ■ ■■■ ■ ■ ■ ■ ■■ ■■■ ■ 529 | ■ 530 | ■ ■ 531 | ■■■ 532 | recognized object, have msg to send 533 | ''' 534 | if (send_valid == 1): 535 | send_valid =0 536 | req_dingapi = request.Request("https://oapi.dingtalk.com/gettoken?corpid="+corpid+"&corpsecret="+corpsecret) 537 | data_from_dingapi = request.urlopen(req_dingapi).read() 538 | data_for_access_token = json.loads(data_from_dingapi.decode("utf-8")) 539 | access_token = data_for_access_token['access_token'] 540 | if(debug!="0"): 541 | log( "[debug]access_token:" + access_token) 542 | url = "https://oapi.dingtalk.com/message/send?access_token="+access_token 543 | headers2dingapi={"Content-Type":"application/json"} 544 | req_dingapi = request.Request(url, data=msg2dingapi_b,headers=headers2dingapi, method="POST") 545 | data_from_dingapi = request.urlopen(req_dingapi).read() 546 | log( data_from_dingapi.decode('utf_8')) 547 | json_from_dingapi = json.loads(data_from_dingapi.decode("utf-8")) 548 | if (json_from_dingapi["errcode"]!=0): 549 | print(msg2dingapi_json_str) 550 | else: # send_valid==0, unhandled object kind 551 | log( "Object "+data_json['object_kind'] +" received. not handled") 552 | if (data_json['object_kind']=="note"): 553 | log( "noteable_type "+data_json['object_attributes']['noteable_type']) 554 | if "object_attributes" in data_json: 555 | if "url" in data_json['object_attributes']: 556 | log( data_json['object_attributes']['url']) 557 | 558 | 559 | ''' 560 | bind dingUid to git email and lab username 561 | query: action=&name=&username=&email= 562 | ''' 563 | def do_GET(self): 564 | method_get_req_dict = {} 565 | global git_members 566 | global lab_members 567 | querypath = urlparse(self.path) 568 | filepath, query = querypath.path, querypath.query 569 | response_text="" 570 | try: 571 | datas = urllib.parse.unquote(querypath.query).split("&") 572 | for data in datas: 573 | key = data.split("=")[0] 574 | value = data.split("=")[1] 575 | method_get_req_dict[key] = value 576 | except: 577 | self.send_response(400) 578 | self.send_header('Content-type',"text; charset=utf-8") 579 | self.end_headers() 580 | self.wfile.write("400 - Bad Request".encode("utf-8")) 581 | log("bad request: "+ querypath.query) 582 | else: 583 | self.send_response(200) 584 | self.send_header('Content-type',"text; charset=utf-8") 585 | self.end_headers() 586 | 587 | if "action" in method_get_req_dict: 588 | 589 | ''' 590 | ■ ■ 591 | ■ ■■ ■ 592 | ■ ■■ ■ 593 | ■ ■ 594 | ■ ■ ■ ■■■ ■ ■ ■ ■ ■■ ■■■ ■ ■■ 595 | ■ ■ ■■ ■■ ■ ■ ■ ■ ■ ■ ■ ■ ■■ 596 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 597 | ■ ■ ■ ■ ■■■ ■ ■ ■■ ■■■■■ ■ 598 | ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ 599 | ■ ■ ■ ■ ■ ■ ■■ ■■ ■ ■ ■■ ■ ■ 600 | ■ ■ ■ ■ ■ ■ ■■■ ■ ■■ ■■■ ■ 601 | 602 | action: add new user link 603 | param: action, name(phone number,primary key), email(optional), username(gitlab username) 604 | ''' 605 | if method_get_req_dict["action"] == "linkuser": 606 | #ding_user_all = configparser.ConfigParser() 607 | #ding_user_all.read("user_all") 608 | #ding_user_list = ding_user_all["ding_userid_all"] 609 | try: 610 | os.system("python3 labFetchUser.py") 611 | except: 612 | log("labFetchUser fail") 613 | else: 614 | log("labFetchUser success") 615 | ding_user_list = read_list_from_db("ding_user_list") 616 | if method_get_req_dict["mobile"] in ding_user_list: 617 | method_get_req_dict["dingUid"] = ding_user_list[method_get_req_dict["mobile"]] 618 | response_text = "User found.\n" 619 | if "email" in method_get_req_dict: 620 | if method_get_req_dict["email"] not in git_members: 621 | #config.set("gitmembers",method_get_req_dict["email"],method_get_req_dict["dingUid"]) 622 | #config.write(open('config.ini', 'w')) 623 | sqlite_linkuser_add(method_get_req_dict["mobile"],method_get_req_dict["email"],"GITEMAIL") 624 | response_text = response_text+"Git email added.\n" 625 | log( "add git email for "+method_get_req_dict["mobile"] + ":"+method_get_req_dict["email"]) 626 | else: 627 | response_text = response_text+"Git email already exists!!!\n" 628 | log( "add git email for "+method_get_req_dict["mobile"] + ":"+method_get_req_dict["email"]+" fail:already exists") 629 | if "username" in method_get_req_dict: 630 | if method_get_req_dict["username"] not in lab_members: 631 | #config.set("labmembers",method_get_req_dict["username"],method_get_req_dict["dingUid"]) 632 | #config.write(open('config.ini', 'w')) 633 | sqlite_linkuser_add(method_get_req_dict["mobile"],method_get_req_dict["username"],"GITLABUSERNAME") 634 | response_text = response_text+"Gitlab username added.\n" 635 | log( "add lab username for "+method_get_req_dict["mobile"]+ ":"+method_get_req_dict["username"]) 636 | else: 637 | response_text = response_text+"Gitlab username already exists!!!\n" 638 | log( "add lab username for "+method_get_req_dict["mobile"]+ ":"+method_get_req_dict["username"]+" fail:already exists") 639 | git_members = read_list_from_db("git_members") 640 | lab_members = read_list_from_db("lab_members") 641 | else: 642 | response_text = "User not found, or already linked.\n" 643 | log( "cannot find unlinked user : "+method_get_req_dict["mobile"]) 644 | elif method_get_req_dict["action"] == "linkfront": 645 | response_text = ''' 646 | 647 | 648 | 649 | 650 |

关联钉钉账号

651 |

根据自己实际情况修改相应字段:

652 | 653 |
654 | action:(默认为linkuser,无需改动)
655 | 656 |
657 | mobile: 必须。你的钉钉注册手机号。
658 | 659 |
660 | username: 必须。你在Gitlab上的Username(你登录时所用的用户名,显示在点开右上角头像后第二行@开头的字段)
661 | 662 |
663 | email: 可选。你在git提交中使用的email(如果你不是代码开发者,则不需要填写)
664 | 665 |
666 |
667 | 668 |
669 | 670 | 671 | 672 | '''.format(webhookurl) 673 | else: 674 | response_text = "error: action error" 675 | log( "error: action error: "+method_get_req_dict["action"]) 676 | 677 | else: # no action param in request 678 | response_text = "error: no action." 679 | log( "error: no action param.") 680 | self.wfile.write(response_text.encode("utf-8")) 681 | def run(): 682 | print('starting server, port', port) 683 | # Server settings 684 | server_address = ('127.0.0.1', port) 685 | httpd = HTTPServer(server_address, gitlab2dingsvr_RequestHandler) 686 | print('running server...') 687 | httpd.serve_forever() 688 | 689 | if __name__ == '__main__': 690 | run() 691 | --------------------------------------------------------------------------------