MonkeyModifier

Change webpage content

当前为 2024-08-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MonkeyModifier
  3. // @namespace https://github.com/JiyuShao/greasyfork-scripts
  4. // @version 2024-08-15
  5. // @description Change webpage content
  6. // @author Jiyu Shao <jiyu.shao@gmail.com>
  7. // @license MIT
  8. // @match *://*/*
  9. // @run-at document-start
  10. // @grant unsafeWindow
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15. // ################### common tools
  16. function replaceTextInNode(node, originalText, replaceText) {
  17. // 如果当前节点是文本节点并且包含 originalText
  18. if (node instanceof Text && node.textContent.includes(originalText)) {
  19. // 替换文本
  20. node.textContent = node.textContent.replace(originalText, replaceText);
  21. }
  22.  
  23. // 如果当前节点有子节点,递归处理每个子节点
  24. if (node.hasChildNodes()) {
  25. node.childNodes.forEach((child) => {
  26. replaceTextInNode(child, originalText, replaceText);
  27. });
  28. }
  29. }
  30.  
  31. function registerMutationObserver(node, config = {}, options = {}) {
  32. const finalConfig = {
  33. attributes: false,
  34. childList: true,
  35. subtree: true,
  36. ...config,
  37. };
  38.  
  39. const finalOptions = {
  40. // 元素的属性发生了变化
  41. attributes: options.attributes || [],
  42. // 子节点列表发生了变化
  43. childList: {
  44. addedNodes:
  45. options.childList.addedNodes ||
  46. [
  47. // {
  48. // filter: (node) => {},
  49. // action: (node) => {},
  50. // }
  51. ],
  52. removedNodes: options.childList.removedNodes || [],
  53. },
  54. // 文本节点的内容发生了变化
  55. characterData: options.characterData || [],
  56. };
  57.  
  58. const observer = new MutationObserver((mutationsList, _observer) => {
  59. mutationsList.forEach((mutation) => {
  60. if (mutation.type === 'attributes') {
  61. finalOptions.attributes.forEach(({ filter, action }) => {
  62. try {
  63. if (filter(mutation.target, mutation)) {
  64. action(mutation.target, mutation);
  65. }
  66. } catch (error) {
  67. console.error(
  68. 'MutationObserver attributes callback failed:',
  69. mutation.target,
  70. error
  71. );
  72. }
  73. });
  74. }
  75. if (mutation.type === 'childList') {
  76. // 检查是否有新增的元素
  77. mutation.addedNodes.forEach((node) => {
  78. finalOptions.childList.addedNodes.forEach(({ filter, action }) => {
  79. try {
  80. if (
  81. [Node.TEXT_NODE, Node.COMMENT_NODE].includes(node.nodeType)
  82. ) {
  83. return;
  84. }
  85. if (filter(node, mutation)) {
  86. action(node, mutation);
  87. }
  88. } catch (error) {
  89. console.error(
  90. 'MutationObserver childList.addedNodes callback failed:',
  91. node,
  92. error
  93. );
  94. }
  95. });
  96. });
  97.  
  98. // 检查是否有删除元素
  99. mutation.removedNodes.forEach((node) => {
  100. finalOptions.childList.removedNodes.forEach((filter, action) => {
  101. try {
  102. if (
  103. [Node.TEXT_NODE, Node.COMMENT_NODE].includes(node.nodeType)
  104. ) {
  105. return;
  106. }
  107. if (filter(node, mutation)) {
  108. action(node, mutation);
  109. }
  110. } catch (error) {
  111. console.error(
  112. 'MutationObserver childList.removedNodes callback failed:',
  113. node,
  114. error
  115. );
  116. }
  117. });
  118. });
  119. }
  120. if (mutation.type === 'characterData') {
  121. finalOptions.characterData.forEach(({ filter, action }) => {
  122. try {
  123. if (filter(mutation.target, mutation)) {
  124. action(mutation.target, mutation);
  125. }
  126. } catch (error) {
  127. console.error(
  128. 'MutationObserver characterData callback failed:',
  129. mutation.target,
  130. error
  131. );
  132. }
  133. });
  134. }
  135. });
  136. });
  137. observer.observe(node, finalConfig);
  138. return observer;
  139. }
  140.  
  141. function registerFetchModifier(modifierList) {
  142. const originalFetch = unsafeWindow.fetch;
  143. unsafeWindow.fetch = function (url, options) {
  144. let finalUrl = url;
  145. let finalOptions = { ...options };
  146. let finalResult = null;
  147. const matchedModifierList = modifierList.filter((e) =>
  148. e.test(finalUrl, finalOptions)
  149. );
  150. for (const currentModifier of matchedModifierList) {
  151. if (currentModifier.prerequest) {
  152. [finalUrl, finalOptions] = currentModifier.prerequest(
  153. finalUrl,
  154. finalOptions
  155. );
  156. }
  157. }
  158. finalResult = originalFetch(finalUrl, finalOptions);
  159. for (const currentModifier of matchedModifierList) {
  160. if (currentModifier.preresponse) {
  161. finalResult = currentModifier.preresponse(finalResult);
  162. }
  163. }
  164. return finalResult;
  165. };
  166. }
  167.  
  168. function registerXMLHttpRequestPolyfill() {
  169. // 保存原始的 XMLHttpRequest 构造函数
  170. const originalXMLHttpRequest = unsafeWindow.XMLHttpRequest;
  171.  
  172. // 定义新的 XMLHttpRequest 构造函数
  173. unsafeWindow.XMLHttpRequest = class extends originalXMLHttpRequest {
  174. constructor() {
  175. super();
  176. this._responseType = ''; // 存储 responseType
  177. this._onreadystatechange = null; // 存储 onreadystatechange 函数
  178. this._onload = null; // 存储 onload 函数
  179. this._sendData = null; // 存储 send 方法的数据
  180. this._headers = {}; // 存储请求头
  181. this._method = null; // 存储请求方法
  182. this._url = null; // 存储请求 URL
  183. this._async = true; // 存储异步标志
  184. this._user = null; // 存储用户名
  185. this._password = null; // 存储密码
  186. this._readyState = XMLHttpRequest.UNSENT; // 存储 readyState
  187. this._status = 0; // 存储状态码
  188. this._statusText = ''; // 存储状态文本
  189. this._response = null; // 存储响应对象
  190. this._responseText = ''; // 存储响应文本
  191. this._responseURL = ''; // 存储响应 URL
  192. }
  193.  
  194. open(method, url, async = true, user = null, password = null) {
  195. this._method = method;
  196. this._url = url;
  197. this._async = async;
  198. this._user = user;
  199. this._password = password;
  200. this._readyState = XMLHttpRequest.OPENED;
  201. }
  202.  
  203. send(data) {
  204. this._sendData = data;
  205. this._sendRequest();
  206. }
  207.  
  208. _sendRequest() {
  209. const self = this;
  210.  
  211. // 根据 responseType 设置 fetch 的返回类型
  212. let fetchOptions = {
  213. method: this._method,
  214. headers: new Headers(this._headers),
  215. };
  216.  
  217. // 设置请求体
  218. if (this._sendData !== null) {
  219. fetchOptions.body = this._sendData;
  220. }
  221.  
  222. if (this.withCredentials) {
  223. fetchOptions.credentials = 'include';
  224. }
  225.  
  226. // 发送 fetch 请求
  227. return unsafeWindow
  228. .fetch(this._url, fetchOptions)
  229. .then((response) => {
  230. self._response = response;
  231. self._status = response.status;
  232. self._statusText = response.statusText;
  233. self._readyState = XMLHttpRequest.DONE;
  234. self._responseURL = self._url;
  235. const responseType = self._responseType || 'text';
  236. // 设置响应类型
  237. switch (responseType) {
  238. case 'json':
  239. return response.json().then((json) => {
  240. self._responseText = JSON.stringify(json);
  241. self._response = json;
  242. self._onreadystatechange && self._onreadystatechange();
  243. self._onload && self._onload();
  244. });
  245. case 'text':
  246. return response.text().then((text) => {
  247. self._responseText = text;
  248. self._response = text;
  249. self._onreadystatechange && self._onreadystatechange();
  250. self._onload && self._onload();
  251. });
  252. case 'blob':
  253. return response.blob().then((blob) => {
  254. self._response = blob;
  255. self._onreadystatechange && self._onreadystatechange();
  256. self._onload && self._onload();
  257. });
  258. }
  259. })
  260. .catch((error) => {
  261. self._readyState = XMLHttpRequest.DONE;
  262. self._status = 0;
  263. self._statusText = 'Network Error';
  264. self._onreadystatechange && self._onreadystatechange();
  265. self._onload && self._onload();
  266. });
  267. }
  268.  
  269. setRequestHeader(name, value) {
  270. this._headers[name] = value;
  271. return this;
  272. }
  273.  
  274. getResponseHeader(name) {
  275. return this._response && this._response.headers
  276. ? this._response.headers.get(name)
  277. : null;
  278. }
  279.  
  280. getAllResponseHeaders() {
  281. return this._response && this._response.headers
  282. ? this._response.headers
  283. : null;
  284. }
  285.  
  286. set onreadystatechange(callback) {
  287. this._onreadystatechange = callback;
  288. }
  289.  
  290. set onload(callback) {
  291. this._onload = callback;
  292. }
  293.  
  294. get readyState() {
  295. return this._readyState;
  296. }
  297.  
  298. set readyState(state) {
  299. this._readyState = state;
  300. }
  301.  
  302. get response() {
  303. return this._response;
  304. }
  305.  
  306. set response(value) {
  307. this._response = value;
  308. }
  309.  
  310. get responseText() {
  311. return this._responseText;
  312. }
  313.  
  314. set responseText(value) {
  315. this._responseText = value;
  316. }
  317.  
  318. get responseURL() {
  319. return this._responseURL;
  320. }
  321.  
  322. set responseURL(value) {
  323. this._responseURL = value;
  324. }
  325.  
  326. get status() {
  327. return this._status;
  328. }
  329.  
  330. set status(value) {
  331. this._status = value;
  332. }
  333.  
  334. get statusText() {
  335. return this._statusText;
  336. }
  337.  
  338. set statusText(value) {
  339. this._statusText = value;
  340. }
  341.  
  342. get responseType() {
  343. return this._responseType;
  344. }
  345.  
  346. set responseType(type) {
  347. this._responseType = type;
  348. }
  349. };
  350. }
  351.  
  352. function downloadCSV(arrayOfData, filename) {
  353. // 处理数据,使其适合 CSV 格式
  354. const csvContent = arrayOfData
  355. .map((row) =>
  356. row.map((cell) => `"${(cell || '').replace(/"/g, '""')}"`).join(',')
  357. )
  358. .join('\n');
  359.  
  360. // 在 CSV 内容前加上 BOM
  361. const bom = '\uFEFF';
  362. const csvContentWithBOM = bom + csvContent;
  363.  
  364. // 将内容转换为 Blob
  365. const blob = new Blob([csvContentWithBOM], {
  366. type: 'text/csv;charset=utf-8;',
  367. });
  368.  
  369. // 创建一个隐藏的可下载链接
  370. const url = URL.createObjectURL(blob);
  371. const link = document.createElement('a');
  372. link.href = url;
  373. link.setAttribute('download', `${filename}.csv`); // 指定文件名
  374. document.body.appendChild(link);
  375. link.click(); // 触发点击事件
  376. document.body.removeChild(link); // 清除链接
  377. URL.revokeObjectURL(url); // 释放 URL 对象
  378. }
  379.  
  380. // ################### 加载前插入样式覆盖
  381. const style = document.createElement('style');
  382. const cssRules = `
  383. .dropdown-submenu--viewmode {
  384. display: none !important;
  385. }
  386. [field=modified] {
  387. display: none !important;
  388. }
  389.  
  390. [data-value=modified] {
  391. display: none !important;
  392. }
  393. [data-value=lastmodify] {
  394. display: none !important;
  395. }
  396.  
  397. [data-grid-field=modified] {
  398. display: none !important;
  399. }
  400.  
  401. [data-field-key=modified] {
  402. display: none !important;
  403. }
  404.  
  405. #Revisions {
  406. display: none !important;
  407. }
  408.  
  409. #ContentModified {
  410. display: none !important;
  411. }
  412.  
  413. [title="最后修改时间"] {
  414. display: none !important;
  415. }
  416.  
  417. .left-tree-bottom__manager-company--wide {
  418. display: none !important;
  419. }
  420.  
  421. .left-tree-narrow .left-tree-bottom__personal--icons > a:nth-child(1) {
  422. display: none !important;
  423. }
  424.  
  425. .data_handover_card {
  426. display: none !important;
  427. }
  428.  
  429. .dtd-select-item-option[label='智能人事'] {
  430. display: none !important;
  431. }
  432.  
  433. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(2) {
  434. display: none !important;
  435. }
  436.  
  437. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(3) {
  438. display: none !important;
  439. }
  440.  
  441. .approve-box .pure-form-container .department-field-view {
  442. display: none !important;
  443. }
  444.  
  445. .approve-box .pure-form-container > div:nth-child(2) {
  446. padding: 0 !important;
  447. }
  448. `;
  449. style.appendChild(document.createTextNode(cssRules));
  450. unsafeWindow.document.head.appendChild(style);
  451.  
  452. // ################### 网页内容加载完成立即执行脚本
  453. unsafeWindow.addEventListener('DOMContentLoaded', function () {
  454. // 监听任务右侧基本信息
  455. const taskRightInfoEles =
  456. unsafeWindow.document.querySelectorAll('#ContentModified');
  457. taskRightInfoEles.forEach((element) => {
  458. const parentDiv = element.closest('div.left_3_col');
  459. if (parentDiv) {
  460. parentDiv.style.display = 'none';
  461. }
  462. });
  463. });
  464.  
  465. // ################### 加载完成动态监听
  466. unsafeWindow.addEventListener('load', function () {
  467. registerMutationObserver(
  468. unsafeWindow.document.body,
  469. {
  470. attributes: false,
  471. childList: true,
  472. subtree: true,
  473. },
  474. {
  475. childList: {
  476. addedNodes: [
  477. // 动态文本替换问题
  478. {
  479. filter: (node, _mutation) => {
  480. return node.textContent.includes('最后修改时间');
  481. },
  482. action: (node, _mutation) => {
  483. replaceTextInNode(node, '最后修改时间', '迭代修改时间');
  484. },
  485. },
  486. // 监听动态弹窗 隐藏设置列表字段-最后修改时间左侧
  487. {
  488. filter: (node, _mutation) => {
  489. return (
  490. node.querySelectorAll &&
  491. node.querySelectorAll('input[value=modified]').length > 0
  492. );
  493. },
  494. action: (node, _mutation) => {
  495. node
  496. .querySelectorAll('input[value=modified]')
  497. .forEach((ele) => {
  498. const parentDiv = ele.closest('div.field');
  499. if (parentDiv) {
  500. parentDiv.style.display = 'none';
  501. }
  502. });
  503. },
  504. },
  505. // 监听动态弹窗 隐藏设置列表字段-最后修改时间右侧
  506. {
  507. filter: (node, _mutation) => {
  508. return (
  509. node.querySelectorAll &&
  510. node.querySelectorAll('span[title=最后修改时间]').length > 0
  511. );
  512. },
  513. action: (node, _mutation) => {
  514. node
  515. .querySelectorAll('span[title=最后修改时间]')
  516. .forEach((ele) => {
  517. const parentDiv = ele.closest('div[role=treeitem]');
  518. if (parentDiv) {
  519. parentDiv.style.display = 'none';
  520. }
  521. });
  522. },
  523. },
  524. // 监听企业微信导出按钮
  525. {
  526. filter: (node, _mutation) => {
  527. return (
  528. node.querySelectorAll &&
  529. node.querySelectorAll('.js_export').length > 0
  530. );
  531. },
  532. action: (node, _mutation) => {
  533. function convertTimestampToTime(timestamp) {
  534. // 创建 Date 对象
  535. const date = new Date(timestamp * 1000); // Unix 时间戳是以秒为单位,而 Date 需要毫秒
  536.  
  537. // 获取小时和分钟
  538. const hours = date.getHours();
  539. const minutes = date.getMinutes();
  540.  
  541. // 确定上午还是下午
  542. const amPm = hours >= 12 ? '下午' : '上午';
  543.  
  544. // 返回格式化的字符串
  545. return `${amPm}${hours}:${minutes
  546. .toString()
  547. .padStart(2, '0')}`;
  548. }
  549. node.querySelectorAll('.js_export').forEach((ele) => {
  550. if (ele.dataset.eventListener === 'true') {
  551. return;
  552. }
  553. ele.dataset.eventListener = 'true';
  554. ele.addEventListener('click', async function (event) {
  555. event.preventDefault();
  556. event.stopPropagation();
  557. const response = await unsafeWindow.fetch(
  558. '/wework_admin/getAdminOperationRecord?lang=zh_CN&f=json&ajax=1&timeZoneInfo%5Bzone_offset%5D=-8',
  559. {
  560. headers: {
  561. 'content-type': 'application/x-www-form-urlencoded',
  562. },
  563. body: unsafeWindow.fetchTmpBody,
  564. method: 'POST',
  565. mode: 'cors',
  566. credentials: 'include',
  567. }
  568. );
  569. const responseJson = await response.json();
  570. const excelData = responseJson.data.operloglist.reduce(
  571. (result, current) => {
  572. const typeMapping = {
  573. 9: '新增部门',
  574. 10: '删除部门',
  575. 11: '移动部门',
  576. 13: '删除成员',
  577. 14: '新增成员',
  578. 15: '更改成员信息',
  579. 21: '更改部门信息',
  580. 23: '登录后台',
  581. 25: '发送邀请',
  582. 36: '修改管理组管理员列表',
  583. 35: '修改管理组应用权限',
  584. 34: '修改管理组通讯录权限',
  585. 88: '修改汇报规则',
  586. 120: '导出相关操作记录',
  587. 162: '批量设置成员信息',
  588. };
  589. const optTypeArray = {
  590. 0: '全部',
  591. 3: '成员与部门变更',
  592. 2: '权限管理变更',
  593. 12: '企业信息管理',
  594. 11: '通讯录与聊天管理',
  595. 13: '外部联系人管理',
  596. 8: '应用变更',
  597. 7: '其他',
  598. };
  599. return [
  600. ...result,
  601. [
  602. convertTimestampToTime(current.operatetime),
  603. current.op_name,
  604. optTypeArray[current.type_oper_1],
  605. typeMapping[current.type] || '其他',
  606. current.data,
  607. current.ip,
  608. ],
  609. ];
  610. },
  611. [
  612. [
  613. '时间',
  614. '操作者',
  615. '操作类型',
  616. '操作行为',
  617. '相关数据',
  618. '操作者IP',
  619. ],
  620. ]
  621. );
  622. downloadCSV(excelData, '管理端操作记录');
  623. });
  624. });
  625. },
  626. },
  627. // 监听钉钉首页-部门修改次数
  628. {
  629. filter: (node, _mutation) => {
  630. const spanDoms = Array.from(
  631. node.querySelectorAll('.admin-panel-v2-module span')
  632. );
  633. return (
  634. node.querySelectorAll &&
  635. (spanDoms.filter((e) =>
  636. ['近1月部门修改次数'].includes(e.innerText)
  637. ).length > 0 ||
  638. spanDoms.filter((e) => e.innerText.includes('项指标预警'))
  639. .length > 0)
  640. );
  641. },
  642. action: (node, _mutation) => {
  643. const spanDoms = Array.from(
  644. node.querySelectorAll('.admin-panel-v2-module span')
  645. );
  646. spanDoms
  647. .filter((e) => ['近1月部门修改次数'].includes(e.innerText))
  648. .forEach((ele) => {
  649. const parentDiv = ele.parentElement.parentElement;
  650. if (parentDiv) {
  651. parentDiv.childNodes[1].childNodes[0].innerText = 1;
  652. parentDiv.childNodes[1].childNodes[3].style.display =
  653. 'none';
  654. }
  655. });
  656. spanDoms
  657. .filter((e) => e.innerText.includes('项指标预警'))
  658. .forEach((ele) => {
  659. const parentDiv = ele.closest('div');
  660. if (parentDiv) {
  661. parentDiv.style.display = 'none';
  662. }
  663. });
  664. },
  665. },
  666. // 监听钉钉通讯录菜单-智能人事
  667. {
  668. filter: (node, _mutation) => {
  669. return (
  670. node.querySelectorAll &&
  671. Array.from(
  672. node.querySelectorAll(
  673. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  674. )
  675. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  676. );
  677. },
  678. action: (node, _mutation) => {
  679. Array.from(
  680. node.querySelectorAll(
  681. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  682. )
  683. )
  684. .filter((e) => ['智能人事'].includes(e.innerText))
  685. .forEach((ele) => {
  686. const parentDiv = ele.closest('li[role=menuitem]');
  687. if (parentDiv) {
  688. parentDiv.style.display = 'none';
  689. }
  690. });
  691. },
  692. },
  693. // 监听钉钉首页-常用应用智能人事
  694. {
  695. filter: (node, _mutation) => {
  696. return (
  697. node.querySelectorAll &&
  698. Array.from(
  699. node.querySelectorAll('#oa-new-index ul button span')
  700. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  701. );
  702. },
  703. action: (node, _mutation) => {
  704. Array.from(
  705. node.querySelectorAll('#oa-new-index ul button span')
  706. )
  707. .filter((e) => ['智能人事'].includes(e.innerText))
  708. .forEach((ele) => {
  709. const parentDiv = ele.closest('li');
  710. if (parentDiv) {
  711. parentDiv.style.display = 'none';
  712. }
  713. });
  714. },
  715. },
  716. // 监听钉钉工作台-智能人事
  717. {
  718. filter: (node, _mutation) => {
  719. return (
  720. node.querySelectorAll &&
  721. Array.from(
  722. node.querySelectorAll(
  723. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  724. )
  725. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  726. );
  727. },
  728. action: (node, _mutation) => {
  729. Array.from(
  730. node.querySelectorAll(
  731. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  732. )
  733. )
  734. .filter((e) => ['智能人事'].includes(e.innerText))
  735. .forEach((ele) => {
  736. const parentDiv = ele.closest('tr.dtd-table-row');
  737. if (parentDiv) {
  738. parentDiv.style.display = 'none';
  739. }
  740. });
  741. },
  742. },
  743. // 监听钉钉添加管理员-智能人事权限
  744. {
  745. filter: (node, _mutation) => {
  746. return (
  747. node.querySelectorAll &&
  748. Array.from(node.querySelectorAll('[title=智能人事]')).length >
  749. 0
  750. );
  751. },
  752. action: (node, _mutation) => {
  753. Array.from(node.querySelectorAll('[title=智能人事]')).forEach(
  754. (ele) => {
  755. const parentDiv = ele.closest('.dtd-tree-treenode');
  756. if (parentDiv) {
  757. parentDiv.style.display = 'none';
  758. }
  759. }
  760. );
  761. },
  762. },
  763. // 监听钉钉审计日志
  764. {
  765. filter: (node, _mutation) => {
  766. return (
  767. node.querySelectorAll &&
  768. Array.from(
  769. node.querySelectorAll(
  770. '.audit-content tbody tr>td:nth-child(4)>div'
  771. )
  772. ).filter((e) =>
  773. [
  774. '删除部门',
  775. '添加部门',
  776. '部门名称修改',
  777. '微应用修改',
  778. ].includes(e.innerText)
  779. ).length > 0
  780. );
  781. },
  782. action: (node, _mutation) => {
  783. Array.from(
  784. node.querySelectorAll(
  785. '.audit-content tbody tr>td:nth-child(4)>div'
  786. )
  787. )
  788. .filter((e) =>
  789. ['删除部门', '添加部门', '部门名称修改'].includes(
  790. e.innerText
  791. )
  792. )
  793. .forEach((ele) => {
  794. const parentDiv = ele.closest('tr.dtd-table-row');
  795. if (parentDiv) {
  796. parentDiv.style.display = 'none';
  797. }
  798. });
  799.  
  800. Array.from(
  801. node.querySelectorAll(
  802. '.audit-content tbody tr>td:nth-child(4)>div'
  803. )
  804. )
  805. .filter((e) => ['微应用修改'].includes(e.innerText))
  806. .forEach((e) => {
  807. const parentDiv = e.closest('tr.dtd-table-row');
  808. if (
  809. parentDiv &&
  810. parentDiv
  811. .closest('tr.dtd-table-row')
  812. .querySelectorAll('td:nth-child(2)>div>span')[0]
  813. .innerText.includes('马浩然')
  814. ) {
  815. parentDiv.style.display = 'none';
  816. }
  817. });
  818. },
  819. },
  820. // 监听钉钉审计日志-智能人事
  821. {
  822. filter: (node, _mutation) => {
  823. return (
  824. node.querySelectorAll &&
  825. Array.from(
  826. node.querySelectorAll(
  827. '.audit-content tbody tr>td:nth-child(3)>div'
  828. )
  829. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  830. );
  831. },
  832. action: (node, _mutation) => {
  833. Array.from(
  834. node.querySelectorAll(
  835. '.audit-content tbody tr>td:nth-child(3)>div'
  836. )
  837. )
  838. .filter((e) => ['智能人事'].includes(e.innerText))
  839. .forEach((ele) => {
  840. const parentDiv = ele.closest('tr.dtd-table-row');
  841. if (parentDiv) {
  842. parentDiv.style.display = 'none';
  843. }
  844. });
  845. },
  846. },
  847. // 监听钉钉审计日志-导出按钮
  848. {
  849. filter: (node, _mutation) => {
  850. return (
  851. node.querySelectorAll &&
  852. node.querySelectorAll(
  853. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  854. ).length > 0
  855. );
  856. },
  857. action: (node, _mutation) => {
  858. function formatTimestamp(timestamp) {
  859. // 创建 Date 对象
  860. const date = new Date(timestamp);
  861.  
  862. // 获取年、月、日、小时、分钟、秒
  863. const year = date.getFullYear();
  864. const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,所以需要 +1
  865. const day = String(date.getDate()).padStart(2, '0');
  866. const hours = String(date.getHours()).padStart(2, '0');
  867. const minutes = String(date.getMinutes()).padStart(2, '0');
  868. const seconds = String(date.getSeconds()).padStart(2, '0');
  869.  
  870. // 拼接日期和时间
  871. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  872. }
  873. node
  874. .querySelectorAll(
  875. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  876. )
  877. .forEach((ele) => {
  878. if (ele.dataset.eventListener === 'true') {
  879. return;
  880. }
  881. ele.dataset.eventListener = 'true';
  882. ele.addEventListener('click', async function (event) {
  883. event.preventDefault();
  884. event.stopPropagation();
  885. const response = await unsafeWindow.fetch(
  886. '/omp/lwpV2?key=listOpLog&args=%5Bnull%2C0%2C1000%2C%7B%22startTime%22%3A1356969600000%2C%22endTime%22%3A1723651199999%2C%22opStaffIds%22%3A%5B%5D%7D%5D&timestamp=1723622438315',
  887. {
  888. headers: {
  889. accept: 'application/json, text/plain, */*',
  890. 'x-csrf-token': 'aXe7U6eGmTgUbnTStnqSU8',
  891. },
  892. body: null,
  893. method: 'GET',
  894. mode: 'cors',
  895. credentials: 'include',
  896. }
  897. );
  898. const responseJson = await response.json();
  899. responseJson.result = responseJson.result.filter(
  900. (currentData) => {
  901. if (
  902. ['删除部门', '添加部门', '部门名称修改'].includes(
  903. currentData.type.categoryValue
  904. )
  905. ) {
  906. return false;
  907. }
  908. if (
  909. ['微应用修改'].includes(
  910. currentData.type.categoryValue
  911. ) &&
  912. currentData.opName === '马浩然'
  913. ) {
  914. return false;
  915. }
  916. if (
  917. ['智能人事'].includes(
  918. currentData.object.categoryValue
  919. )
  920. ) {
  921. return false;
  922. }
  923. return true;
  924. }
  925. );
  926. const excelData = responseJson.result.reduce(
  927. (result, current) => {
  928. return [
  929. ...result,
  930. [
  931. formatTimestamp(current.opTime),
  932. current.opName,
  933. current.object.categoryValue,
  934. current.type.categoryValue,
  935. current.content || '',
  936. ],
  937. ];
  938. },
  939. [['时间', '操作者', '事件对象', '事件类型', '详细数据']]
  940. );
  941. downloadCSV(excelData, '审计日志信息');
  942. });
  943. });
  944. },
  945. },
  946. ],
  947. },
  948. }
  949. );
  950. });
  951.  
  952. // ################### 替换请求
  953. if (
  954. unsafeWindow.location.pathname.startsWith('/wework_admin') &&
  955. !unsafeWindow.location.href.includes('loginpage_wx')
  956. ) {
  957. registerFetchModifier([
  958. {
  959. test: (url, options) => {
  960. return url.includes('/wework_admin/getAdminOperationRecord');
  961. },
  962. prerequest: (url, options) => {
  963. options.body = options.body
  964. .split('&')
  965. .reduce((result, current) => {
  966. let [key, value] = current.split('=');
  967. if (key === 'limit') {
  968. value = 500;
  969. }
  970. return [...result, `${key}=${value}`];
  971. }, [])
  972. .join('&');
  973. unsafeWindow.fetchTmpBody = options.body;
  974. return [url, options];
  975. },
  976. preresponse: async (responsePromise) => {
  977. const response = await responsePromise;
  978. let responseJson = await response.json();
  979. responseJson.data.operloglist = responseJson.data.operloglist.filter(
  980. (e) => e.type_oper_1 !== 3
  981. );
  982. responseJson.data.total = responseJson.data.operloglist.length;
  983. return new Response(JSON.stringify(responseJson), {
  984. headers: response.headers,
  985. ok: response.ok,
  986. redirected: response.redirected,
  987. status: response.status,
  988. statusText: response.statusText,
  989. type: response.type,
  990. url: response.url,
  991. });
  992. },
  993. },
  994. ]);
  995. registerXMLHttpRequestPolyfill();
  996. }
  997. // registerXMLHttpRequestPolyfill();
  998. })();