// ==UserScript==
// @name 钉钉审批流程加强插件
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 用于加强钉钉反人类的审批流程设置,提供流程后可自动生成审批规则
// @author $(ghsot)
// @match https://aflow.dingtalk.com/dingtalk/web/query/processDesign*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
/*let define=Object.defineProperty;
Object.defineProperty=function(){
//console.log(arguments);
define.apply(Object,arguments);
}*/
/*function print(title,data){
console.log('==============='+title+'===============start');
console.log(data);
console.log('==============='+title+'===============end');
}*/
//供外部代码运行的环境
let sandbox={
//开始指定指定代码
exec:(code)=>{
eval(code);
},
//抓取原数据
dataBefore:null,
//抓取模板
template:null,
//提供的方法
//获取指定目录下成员列表,根目录不传
readCompanyList:null,
//搜索指定成员
searchFromCompany:null,
//获取角色列表
readRoleList:null,
//保存条件列表
saveConditions:null,
//保存主要条件部分
saveRule:null,
//保存审批去重
saveProcessSetting:null,
//保存提示规则
saveNotice:null,
//去重规则 processCode:xx,settings:[{"type":"proc_append_enable","value":"n"},{"type":"proc_duplicate_approval","value":"no"}]
duplicateList:[
{name:'不自动去重',value:'allow'},
{name:'同一个审批人在流程中出现多次时,仅保留第一个',value:'no_after'},
{name:'同一个审批人在流程中出现多次时,仅保留最后一个',value:'no'},
{name:'仅同一个审批人连续出现时,自动去重',value:'allow_interval'}
],
//通知规则 processCode:xx,noticePosition:'start'
noticeList:[
{name:'仅全部同意后通知',value:'finish'},
{name:'仅发起时通知',value:'start'},
{name:'发起时和全部同意后均通知',value:'start_finish'}
]
};
window.sandbox=sandbox;
//内部代码执行闭包
(function(){
//hook field
function field(obj,name,getter,setter){
let val=obj[name];
Object.defineProperty(obj,name,{
get(){
val=getter?getter(val):val;
return val;
},
set(value){
val=setter?setter(value,val):val;
}
})
}
//当前流程process
let dingProcess;
//当前流程ID
let dingId;
//检查工作是否完成
let initStarted=false;
function checkMisson(){
if(sandbox.dataBefore&&sandbox.template&&!initStarted){
initStarted=true;
//向网页中添加元素
let content=document.getElementsByClassName('approval-page-container')[0];
let div=document.createElement('div');
div.innerHTML='<input type="file"><p>加载一个脚本文件开始执行任务</p>';
let input=div.children[0];
//给input赋予事件
input.onchange=()=>{
let file=input.files[0];
let reader=new FileReader();
reader.onload=(res)=>{
sandbox.exec(res.target.result);
}
reader.readAsText(file);
}
content.insertBefore(div,content.children[0]);
}
}
//覆盖fetch
let fetch=window.fetch;
window.fetch=null;
let xhr=window.XMLHttpRequest;
//封装一个ajax
let http = {
qs(data){
let str = ""
for(let i in data){
if(str)
{
str+='&';
}
str += i+'='+data[i];
}
return str;
},
get(url,data,func){
let x = new xhr();
x.onreadystatechange = function(){
if(x.readyState== 4){
if(x.status==200){
func(JSON.parse(x.responseText));
}else{
func(null);
}
}
}
x.withCredentials=true;
let str = this.qs(data);
x.open('GET',url+(str?('?'+str):''),true);
x.send();
},
post(url,data,func,json){
let x = new xhr();
x.onreadystatechange = function(){
if(x.readyState== 4){
if(x.status==200){
func(JSON.parse(x.responseText));
}else{
func(null);
}
}
}
x.withCredentials=true;
x.open('POST',url,true);
if(json){
x.setRequestHeader('content-type','application/json');
//发送
x.send(JSON.stringify(data));
}else{
x.setRequestHeader('content-type','application/x-www-form-urlencoded');
let str= this.qs(data);
x.send(str);
}
}
};
//获取指定目录公司列表
sandbox.readCompanyList=(id)=>{
id=id||-1;
return new Promise((resolve,reject)=>{
let url='https://oa.dingtalk.com/omp/lwpV2';
http.get(url,{
timestamp:new Date().getTime(),
key:'ContactGetOrgNodeList',
args:JSON.stringify([id,0,null,0,30,{"appId":-4,"nodeType":2,"type":"w"}])
},(res)=>{
//console.log(res)
res!=null?resolve(res.result.values):reject();
});
})
}
//搜索某一个人名字
sandbox.searchFromCompany=(keyword)=>{
keyword=keyword||'';
return new Promise((resolve,reject)=>{
let url='https://oa.dingtalk.com/omp/lwpV2';
http.get(url,{
timestamp:new Date().getTime(),
key:'ContactSearchList',
args:JSON.stringify([keyword,0,0,30,{"appId":-4,"type":"w"}])
},(res)=>{
res!=null?resolve(res.result.values):reject();
})
})
}
//获取角色列表
sandbox.readRoleList=()=>{
return new Promise((resolve,reject)=>{
let url='https://oa.dingtalk.com/omp/lwpV2?timestamp=1532164518661&key=LabelGetGroupInfoByPage&args=[null,1,0,100000000]';
http.get(url,{},(res)=>{
res!=null?resolve(res.result):reject();
})
})
}
//保存条件列表 -1 发起人
sandbox.saveConditions=(list)=>{
let sendList=[];
let data={};
data.processCode=dingProcess;
for(let item of list){
if(item==-1){
//发起人条件
sendList.push({id:'dingtalk_origin_dept',label:'发起人',type:'dept',value:[]});
}else{
//自定义模板条件
for(let temp of sandbox.template){
if(temp.props.label==item){
//找到了
sendList.push({id:temp.props.id,label:item,type:'value',value:[]});
break;
}
}
}
}
data.conditionRule=JSON.stringify(sendList);
return new Promise((resolve,reject)=>{
http.post('https://aflow.dingtalk.com/dingtalk/web/query/rule/setConditionRule.json',data,(res)=>{
res!=null?resolve(res):reject();
})
})
}
//保存主要规则数据
//单位类型
//target_approval 0一个人 type:~ approvals[]:id
//target_label 1一个组 type:~ labels[]:id labelNames[]:name
//target_managers_labels 2从直属上级一直到该组 type:~ labels[]:id labelNames[]:name
const humanList=[
'target_approval',
'target_label',
'target_managers_labels'
];
//条件类型
/*
0 选择框 paramValues为符合条件的所有选项数组
1 数字框
*/
const condList=[
{
//数字输入
type:'dingtalk_actioner_range_condition',
classAlias:'DingTalkActionerRangeConfExtensionD0'
},
{
//单选框
type:'dingtalk_actioner_value_conditon',
classAlias:'DingTalkActionerValueConfExtensionD0'
}
];
/*
rule:
type 0 默认规则 1判断规则
notifiers 抄送者
type 类型
id ID
name 名称,approval不需要
rules 规则
type 类型
id ID
name 名称,approval不需要
senders 发起人 发起人条件
type 0 一个人 1一个组
id ID
name 名字
count 组的时候提供数量
avatar 人的时候提供
conds 其他条件
type condList里的Index
name 条件名字
key 条件来自于哪个控件ID
values 如果是选择框,提供满足选择的list
> 如果是数字输入框,提供数字区间,5个key可供使用
>=
<
<=
==
*/
sandbox.saveRule=(rules)=>{
try{
let data={};
data.id=dingId;
data.processCode=dingProcess;
data.ruleType='dingtalk_multi_actioner';
let content={};
content.rules={type:'dingtalk_actioner',rules:[]};
content.type='dingtalk_multi_actioner';
content.multiRules=[];
for(let rule of rules){
let item={};
//流程类型 默认还是判断
item.type=rule.type?'dingtalk_actioner_dept_condition':'dingtalk_actioner_default';
//未知值 写死0
item.exclMgrDeptsDepth=0;
//状态值 写死0
item.status='0';
//加入抄送者
item.notifiers=[];
for(let notifier of rule.notifiers){
let n={};
n.type=humanList[notifier.type];
if(notifier.type==0){
n.approvals=[notifier.id];
}else{
n.labels=[notifier.id];
n.labelNames=[notifier.name];
}
item.notifiers.push(n);
}
//加入审批者
item.rules=[];
for(let ruleItem of rule.rules){
let n={};
n.type=humanList[ruleItem.type];
if(rule.type==0){
n.approvals=[ruleItem.id];
}else{
n.labels=[ruleItem.id];
n.labelNames=[ruleItem.name];
}
item.rules.push(n);
}
//加入sender
if(rule.senders){
item.paramKey='dingtalk_origin_dept';
item.paramLabel='发起人';
item.conds=[];
for(let sender of rule.senders){
let n={};
n.type='user';
n.value=sender.id;
if(sender.type==0){
n.attrs={
name:sender.name,
avatar:sender.avatar
}
}else{
n.attrs={
name:sender.name,
memberCount:sender.count
}
}
item.conds.push(n);
}
}
//加入其他条件 递归
let addCond=(list)=>{
let content={};
let cond=list.shift();
//加入数据
content.classAlias=condList[cond.type].classAlias;
content.type=condList[cond.type].type;
content.exclMgrDeptsDepth=0;
content.status='1';
content.paramLabel=cond.name;
switch(cond.type){
case 0:
content.upperBound=cond['<']||'';
content.lowerBoundNotEqual=cond['>']||'';
content.lowerBound=cond['>=']||'';
content.bondEqual=cond['=']||'';
content.upperBoundEqual=cond['<=']||'';
break;
case 1:
content.paramValues=cond.values;
break;
}
//继续递归
if(list.length>0){
//还有数据,继续递归
content.multiRules=addCond(list);
}else{
//到尽头了,添加数据
//再次添加一次审批人和抄送,我曹我也不知道为什么
//抄送人
/*content.notifiers=[];
for(let notifier of rule.notifiers){
let n={};
}*/
}
return content;
}
//开始调用
if(rule.cond&&rule.cond.length>0){
item.multiRules=addCond(rule.cond);
}
content.multiRules.push(item);
}
data.ruleConf=JSON.stringify(content);
return new Promise((resolve,reject)=>{
http.post('https://aflow.dingtalk.com/dingtalk/web/query/rule/setRuleConfInfo.json',data,(res)=>{
res!=null?resolve(res):reject();
})
})
}catch(e){console.log(e);}
}
//保存去重设置
sandbox.saveProcessSetting=(duplicate_approval)=>{
let data={};
data.processCode=dingProcess;
let settings=[];
settings.push({type:'proc_append_enable',value:'n'});
settings.push({type:'proc_duplicate_approval',value:duplicate_approval});
data.settings=JSON.stringify(settings);
return new Promise((resolve,reject)=>{
http.post('https://aflow.dingtalk.com/dingtalk/web/query/process/setProcessSetting.json',data,(res)=>{
res!=null?resolve(res):reject();
})
})
}
//保存提示信息
sandbox.saveNotice=(rule)=>{
let data={};
data.processCode=dingProcess;
data.noticePosition=rule;
return new Promise((resolve,reject)=>{
http.post('https://aflow.dingtalk.com/dingtalk/web/query/notice/setNoticePosition.json',data,(res)=>{
res!=null?resolve(res):reject();
})
})
}
//放入window开始测试
//window.testFunc=readRoleList;
//开始代理原类
window.XMLHttpRequest=function(){
let obj=new xhr();
//内部的任务开关
let catchDataBefore=false;
let catchTemplate=false;
//代理所有内容
for(let key in obj){
if(typeof obj[key]=='function'){
switch(key){
case 'open':
this[key]=open;
break;
case 'send':
this[key]=send;
break;
default:
this[key]=function(){
obj[key].apply(obj,arguments);
}
break;
}
}else{
field(this,key,function(){
//当数据被获取时,也同时获取一份
switch(key){
case 'response':
//获取之前数据
if(catchDataBefore){
catchDataBefore=false;
let data=JSON.parse(obj.responseText);
sandbox.dataBefore=JSON.parse(data.data.ruleConf);
dingProcess=data.data.name;
dingId=data.data.id;
console.log(dingProcess,dingId);
checkMisson();
//print('规则数据',dataBefore);
}
//获取模板
if(catchTemplate){
catchTemplate=false;
let data=JSON.parse(obj.responseText);
let inner=JSON.parse(data.data.content);
sandbox.template=[];
for(let item of inner.items){
if(item.props.required)
{
sandbox.template.push(item);
}
}
checkMisson();
//print('模板',template);
}
break;
}
return obj[key];
},function(val){
obj[key]=val;
})
}
}
//open方法
function open(){
//console.log('execexec');
//抓取原数据
if(arguments[1].indexOf('getProcessRuleConfInfo.json')>=0&&!sandbox.dataBefore){
sandbox.dataBefore=1;
catchDataBefore=true;
}
//抓取审核模板
if(arguments[1].indexOf('getForm.json')>=0&&!sandbox.template){
//console.log('我执行了');
sandbox.template=1;
catchTemplate=true;
}
//执行原方法
obj.open.apply(obj,arguments);
}
//send方法
function send(){
obj.responseType='';
obj.send.apply(obj,arguments);
}
};
})();
})();