Greasy Fork 还支持 简体中文。

MonkeyModifier

Change webpage content

目前為 2024-08-16 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name MonkeyModifier
  3. // @namespace https://github.com/JiyuShao/greasyfork-scripts
  4. // @version 2024-08-16
  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. .dtd-select-item-option[label='管理组'] {
  434. display: none !important;
  435. }
  436.  
  437. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(2) {
  438. display: none !important;
  439. }
  440.  
  441. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(3) {
  442. display: none !important;
  443. }
  444.  
  445. .approve-box .pure-form-container .department-field-view {
  446. display: none !important;
  447. }
  448.  
  449. .approve-box .pure-form-container > div:nth-child(2) {
  450. padding: 0 !important;
  451. }
  452. `;
  453. style.appendChild(document.createTextNode(cssRules));
  454. unsafeWindow.document.head.appendChild(style);
  455.  
  456. // ################### 网页内容加载完成立即执行脚本
  457. unsafeWindow.addEventListener('DOMContentLoaded', function () {
  458. // 监听任务右侧基本信息
  459. const taskRightInfoEles =
  460. unsafeWindow.document.querySelectorAll('#ContentModified');
  461. taskRightInfoEles.forEach((element) => {
  462. const parentDiv = element.closest('div.left_3_col');
  463. if (parentDiv) {
  464. parentDiv.style.display = 'none';
  465. }
  466. });
  467. });
  468.  
  469. // ################### 加载完成动态监听
  470. unsafeWindow.addEventListener('load', function () {
  471. registerMutationObserver(
  472. unsafeWindow.document.body,
  473. {
  474. attributes: false,
  475. childList: true,
  476. subtree: true,
  477. },
  478. {
  479. childList: {
  480. addedNodes: [
  481. // 动态文本替换问题
  482. {
  483. filter: (node, _mutation) => {
  484. return node.textContent.includes('最后修改时间');
  485. },
  486. action: (node, _mutation) => {
  487. replaceTextInNode(node, '最后修改时间', '迭代修改时间');
  488. },
  489. },
  490. // 监听动态弹窗 隐藏设置列表字段-最后修改时间左侧
  491. {
  492. filter: (node, _mutation) => {
  493. return (
  494. node.querySelectorAll &&
  495. node.querySelectorAll('input[value=modified]').length > 0
  496. );
  497. },
  498. action: (node, _mutation) => {
  499. node
  500. .querySelectorAll('input[value=modified]')
  501. .forEach((ele) => {
  502. const parentDiv = ele.closest('div.field');
  503. if (parentDiv) {
  504. parentDiv.style.display = 'none';
  505. }
  506. });
  507. },
  508. },
  509. // 监听动态弹窗 隐藏设置列表字段-最后修改时间右侧
  510. {
  511. filter: (node, _mutation) => {
  512. return (
  513. node.querySelectorAll &&
  514. node.querySelectorAll('span[title=最后修改时间]').length > 0
  515. );
  516. },
  517. action: (node, _mutation) => {
  518. node
  519. .querySelectorAll('span[title=最后修改时间]')
  520. .forEach((ele) => {
  521. const parentDiv = ele.closest('div[role=treeitem]');
  522. if (parentDiv) {
  523. parentDiv.style.display = 'none';
  524. }
  525. });
  526. },
  527. },
  528. // 监听企业微信导出按钮
  529. {
  530. filter: (node, _mutation) => {
  531. return (
  532. node.querySelectorAll &&
  533. node.querySelectorAll('.js_export').length > 0
  534. );
  535. },
  536. action: (node, _mutation) => {
  537. function convertTimestampToTime(timestamp) {
  538. // 创建 Date 对象
  539. const date = new Date(timestamp * 1000); // Unix 时间戳是以秒为单位,而 Date 需要毫秒
  540.  
  541. // 获取小时和分钟
  542. const hours = date.getHours();
  543. const minutes = date.getMinutes();
  544.  
  545. // 确定上午还是下午
  546. const amPm = hours >= 12 ? '下午' : '上午';
  547.  
  548. // 返回格式化的字符串
  549. return `${amPm}${hours}:${minutes
  550. .toString()
  551. .padStart(2, '0')}`;
  552. }
  553. node.querySelectorAll('.js_export').forEach((ele) => {
  554. if (ele.dataset.eventListener === 'true') {
  555. return;
  556. }
  557. ele.dataset.eventListener = 'true';
  558. ele.addEventListener('click', async function (event) {
  559. event.preventDefault();
  560. event.stopPropagation();
  561. const response = await unsafeWindow.fetch(
  562. '/wework_admin/getAdminOperationRecord?lang=zh_CN&f=json&ajax=1&timeZoneInfo%5Bzone_offset%5D=-8',
  563. {
  564. headers: {
  565. 'content-type': 'application/x-www-form-urlencoded',
  566. },
  567. body: unsafeWindow.fetchTmpBody,
  568. method: 'POST',
  569. mode: 'cors',
  570. credentials: 'include',
  571. }
  572. );
  573. const responseJson = await response.json();
  574. const excelData = responseJson.data.operloglist.reduce(
  575. (result, current) => {
  576. const typeMapping = {
  577. 9: '新增部门',
  578. 10: '删除部门',
  579. 11: '移动部门',
  580. 13: '删除成员',
  581. 14: '新增成员',
  582. 15: '更改成员信息',
  583. 21: '更改部门信息',
  584. 23: '登录后台',
  585. 25: '发送邀请',
  586. 36: '修改管理组管理员列表',
  587. 35: '修改管理组应用权限',
  588. 34: '修改管理组通讯录权限',
  589. 88: '修改汇报规则',
  590. 120: '导出相关操作记录',
  591. 162: '批量设置成员信息',
  592. };
  593. const optTypeArray = {
  594. 0: '全部',
  595. 3: '成员与部门变更',
  596. 2: '权限管理变更',
  597. 12: '企业信息管理',
  598. 11: '通讯录与聊天管理',
  599. 13: '外部联系人管理',
  600. 8: '应用变更',
  601. 7: '其他',
  602. };
  603. return [
  604. ...result,
  605. [
  606. convertTimestampToTime(current.operatetime),
  607. current.op_name,
  608. optTypeArray[current.type_oper_1],
  609. typeMapping[current.type] || '其他',
  610. current.data,
  611. current.ip,
  612. ],
  613. ];
  614. },
  615. [
  616. [
  617. '时间',
  618. '操作者',
  619. '操作类型',
  620. '操作行为',
  621. '相关数据',
  622. '操作者IP',
  623. ],
  624. ]
  625. );
  626. downloadCSV(excelData, '管理端操作记录');
  627. });
  628. });
  629. },
  630. },
  631. // 监听钉钉首页-部门修改次数
  632. {
  633. filter: (node, _mutation) => {
  634. const spanDoms = Array.from(
  635. node.querySelectorAll('.admin-panel-v2-module span')
  636. );
  637. return (
  638. node.querySelectorAll &&
  639. (spanDoms.filter((e) =>
  640. ['近1月部门修改次数'].includes(e.innerText)
  641. ).length > 0 ||
  642. spanDoms.filter((e) => e.innerText.includes('项指标预警'))
  643. .length > 0)
  644. );
  645. },
  646. action: (node, _mutation) => {
  647. const spanDoms = Array.from(
  648. node.querySelectorAll('.admin-panel-v2-module span')
  649. );
  650. spanDoms
  651. .filter((e) => ['近1月部门修改次数'].includes(e.innerText))
  652. .forEach((ele) => {
  653. const parentDiv = ele.parentElement.parentElement;
  654. if (parentDiv) {
  655. parentDiv.childNodes[1].childNodes[0].innerText = 1;
  656. parentDiv.childNodes[1].childNodes[3].style.display =
  657. 'none';
  658. }
  659. });
  660. spanDoms
  661. .filter((e) => e.innerText.includes('项指标预警'))
  662. .forEach((ele) => {
  663. const parentDiv = ele.closest('div');
  664. if (parentDiv) {
  665. parentDiv.style.display = 'none';
  666. }
  667. });
  668. },
  669. },
  670. // 监听钉钉通讯录菜单-智能人事
  671. {
  672. filter: (node, _mutation) => {
  673. return (
  674. node.querySelectorAll &&
  675. Array.from(
  676. node.querySelectorAll(
  677. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  678. )
  679. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  680. );
  681. },
  682. action: (node, _mutation) => {
  683. Array.from(
  684. node.querySelectorAll(
  685. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  686. )
  687. )
  688. .filter((e) => ['智能人事'].includes(e.innerText))
  689. .forEach((ele) => {
  690. const parentDiv = ele.closest('li[role=menuitem]');
  691. if (parentDiv) {
  692. parentDiv.style.display = 'none';
  693. }
  694. });
  695. },
  696. },
  697. // 监听钉钉首页-常用应用智能人事
  698. {
  699. filter: (node, _mutation) => {
  700. return (
  701. node.querySelectorAll &&
  702. Array.from(
  703. node.querySelectorAll('#oa-new-index ul button span')
  704. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  705. );
  706. },
  707. action: (node, _mutation) => {
  708. Array.from(
  709. node.querySelectorAll('#oa-new-index ul button span')
  710. )
  711. .filter((e) => ['智能人事'].includes(e.innerText))
  712. .forEach((ele) => {
  713. const parentDiv = ele.closest('li');
  714. if (parentDiv) {
  715. parentDiv.style.display = 'none';
  716. }
  717. });
  718. },
  719. },
  720. // 监听钉钉工作台-智能人事
  721. {
  722. filter: (node, _mutation) => {
  723. return (
  724. node.querySelectorAll &&
  725. Array.from(
  726. node.querySelectorAll(
  727. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  728. )
  729. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  730. );
  731. },
  732. action: (node, _mutation) => {
  733. Array.from(
  734. node.querySelectorAll(
  735. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  736. )
  737. )
  738. .filter((e) => ['智能人事'].includes(e.innerText))
  739. .forEach((ele) => {
  740. const parentDiv = ele.closest('tr.dtd-table-row');
  741. if (parentDiv) {
  742. parentDiv.style.display = 'none';
  743. }
  744. });
  745. },
  746. },
  747. // 监听钉钉添加管理员-智能人事权限
  748. {
  749. filter: (node, _mutation) => {
  750. return (
  751. node.querySelectorAll &&
  752. Array.from(node.querySelectorAll('[title=智能人事]')).length >
  753. 0
  754. );
  755. },
  756. action: (node, _mutation) => {
  757. Array.from(node.querySelectorAll('[title=智能人事]')).forEach(
  758. (ele) => {
  759. const parentDiv = ele.closest('.dtd-tree-treenode');
  760. if (parentDiv) {
  761. parentDiv.style.display = 'none';
  762. }
  763. }
  764. );
  765. },
  766. },
  767. // 监听钉钉审计日志
  768. {
  769. filter: (node, _mutation) => {
  770. return (
  771. node.querySelectorAll &&
  772. Array.from(
  773. node.querySelectorAll(
  774. '.audit-content tbody tr>td:nth-child(4)>div'
  775. )
  776. ).filter((e) =>
  777. [
  778. '删除部门',
  779. '添加部门',
  780. '部门名称修改',
  781. '微应用修改',
  782. ].includes(e.innerText)
  783. ).length > 0
  784. );
  785. },
  786. action: (node, _mutation) => {
  787. Array.from(
  788. node.querySelectorAll(
  789. '.audit-content tbody tr>td:nth-child(4)>div'
  790. )
  791. )
  792. .filter((e) =>
  793. ['删除部门', '添加部门', '部门名称修改'].includes(
  794. e.innerText
  795. )
  796. )
  797. .forEach((ele) => {
  798. const parentDiv = ele.closest('tr.dtd-table-row');
  799. if (parentDiv) {
  800. parentDiv.style.display = 'none';
  801. }
  802. });
  803.  
  804. Array.from(
  805. node.querySelectorAll(
  806. '.audit-content tbody tr>td:nth-child(4)>div'
  807. )
  808. )
  809. .filter((e) => ['微应用修改'].includes(e.innerText))
  810. .forEach((e) => {
  811. const parentDiv = e.closest('tr.dtd-table-row');
  812. if (
  813. parentDiv &&
  814. parentDiv
  815. .closest('tr.dtd-table-row')
  816. .querySelectorAll('td:nth-child(2)>div>span')[0]
  817. .innerText.includes('马浩然')
  818. ) {
  819. parentDiv.style.display = 'none';
  820. }
  821. });
  822. },
  823. },
  824. // 监听钉钉审计日志-智能人事、管理组
  825. {
  826. filter: (node, _mutation) => {
  827. return (
  828. node.querySelectorAll &&
  829. Array.from(
  830. node.querySelectorAll(
  831. '.audit-content tbody tr>td:nth-child(3)>div'
  832. )
  833. ).filter((e) => ['智能人事', '管理组'].includes(e.innerText))
  834. .length > 0
  835. );
  836. },
  837. action: (node, _mutation) => {
  838. Array.from(
  839. node.querySelectorAll(
  840. '.audit-content tbody tr>td:nth-child(3)>div'
  841. )
  842. )
  843. .filter((e) => ['智能人事', '管理组'].includes(e.innerText))
  844. .forEach((ele) => {
  845. const parentDiv = ele.closest('tr.dtd-table-row');
  846. if (parentDiv) {
  847. parentDiv.style.display = 'none';
  848. }
  849. });
  850. },
  851. },
  852. // 监听钉钉审计日志-导出按钮
  853. {
  854. filter: (node, _mutation) => {
  855. return (
  856. node.querySelectorAll &&
  857. node.querySelectorAll(
  858. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  859. ).length > 0
  860. );
  861. },
  862. action: (node, _mutation) => {
  863. function formatTimestamp(timestamp) {
  864. // 创建 Date 对象
  865. const date = new Date(timestamp);
  866.  
  867. // 获取年、月、日、小时、分钟、秒
  868. const year = date.getFullYear();
  869. const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,所以需要 +1
  870. const day = String(date.getDate()).padStart(2, '0');
  871. const hours = String(date.getHours()).padStart(2, '0');
  872. const minutes = String(date.getMinutes()).padStart(2, '0');
  873. const seconds = String(date.getSeconds()).padStart(2, '0');
  874.  
  875. // 拼接日期和时间
  876. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  877. }
  878. node
  879. .querySelectorAll(
  880. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  881. )
  882. .forEach((ele) => {
  883. if (ele.dataset.eventListener === 'true') {
  884. return;
  885. }
  886. ele.dataset.eventListener = 'true';
  887. ele.addEventListener('click', async function (event) {
  888. event.preventDefault();
  889. event.stopPropagation();
  890. function getAllCookies() {
  891. const cookiesArray = document.cookie.split('; ');
  892. const cookiesObj = {};
  893. cookiesArray.forEach((cookie) => {
  894. const parts = cookie.split('=');
  895. cookiesObj[parts[0]] = decodeURIComponent(parts[1]);
  896. });
  897. return cookiesObj;
  898. }
  899. const response = await unsafeWindow.fetch(
  900. '/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',
  901. {
  902. headers: {
  903. accept: 'application/json, text/plain, */*',
  904. 'x-csrf-token': getAllCookies()['csrf_token'],
  905. },
  906. body: null,
  907. method: 'GET',
  908. mode: 'cors',
  909. credentials: 'include',
  910. }
  911. );
  912. const responseJson = await response.json();
  913. responseJson.result = responseJson.result.filter(
  914. (currentData) => {
  915. if (
  916. ['删除部门', '添加部门', '部门名称修改'].includes(
  917. currentData.type.categoryValue
  918. )
  919. ) {
  920. return false;
  921. }
  922. if (
  923. ['微应用修改'].includes(
  924. currentData.type.categoryValue
  925. ) &&
  926. currentData.opName === '马浩然'
  927. ) {
  928. return false;
  929. }
  930. if (
  931. ['智能人事', '管理组'].includes(
  932. currentData.object.categoryValue
  933. )
  934. ) {
  935. return false;
  936. }
  937. return true;
  938. }
  939. );
  940. const excelData = responseJson.result.reduce(
  941. (result, current) => {
  942. return [
  943. ...result,
  944. [
  945. formatTimestamp(current.opTime),
  946. current.opName,
  947. current.object.categoryValue,
  948. current.type.categoryValue,
  949. current.content || '',
  950. ],
  951. ];
  952. },
  953. [['时间', '操作者', '事件对象', '事件类型', '详细数据']]
  954. );
  955. downloadCSV(excelData, '审计日志信息');
  956. });
  957. });
  958. },
  959. },
  960. ],
  961. },
  962. }
  963. );
  964. });
  965.  
  966. // ################### 替换请求
  967. if (
  968. unsafeWindow.location.pathname.startsWith('/wework_admin') &&
  969. !unsafeWindow.location.href.includes('loginpage_wx')
  970. ) {
  971. registerFetchModifier([
  972. {
  973. test: (url, options) => {
  974. return url.includes('/wework_admin/getAdminOperationRecord');
  975. },
  976. prerequest: (url, options) => {
  977. options.body = options.body
  978. .split('&')
  979. .reduce((result, current) => {
  980. let [key, value] = current.split('=');
  981. if (key === 'limit') {
  982. value = 500;
  983. }
  984. return [...result, `${key}=${value}`];
  985. }, [])
  986. .join('&');
  987. unsafeWindow.fetchTmpBody = options.body;
  988. return [url, options];
  989. },
  990. preresponse: async (responsePromise) => {
  991. const response = await responsePromise;
  992. let responseJson = await response.json();
  993. responseJson.data.operloglist = responseJson.data.operloglist.filter(
  994. (e) => e.type_oper_1 !== 3
  995. );
  996. responseJson.data.total = responseJson.data.operloglist.length;
  997. return new Response(JSON.stringify(responseJson), {
  998. headers: response.headers,
  999. ok: response.ok,
  1000. redirected: response.redirected,
  1001. status: response.status,
  1002. statusText: response.statusText,
  1003. type: response.type,
  1004. url: response.url,
  1005. });
  1006. },
  1007. },
  1008. ]);
  1009. registerXMLHttpRequestPolyfill();
  1010. }
  1011. // registerXMLHttpRequestPolyfill();
  1012. })();