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._onloadend = null; // 存储 onloadend 函数
  180. this._sendData = null; // 存储 send 方法的数据
  181. this._headers = {}; // 存储请求头
  182. this._method = null; // 存储请求方法
  183. this._url = null; // 存储请求 URL
  184. this._async = true; // 存储异步标志
  185. this._user = null; // 存储用户名
  186. this._password = null; // 存储密码
  187. this._readyState = XMLHttpRequest.UNSENT; // 存储 readyState
  188. this._status = 0; // 存储状态码
  189. this._statusText = ''; // 存储状态文本
  190. this._response = null; // 存储响应对象
  191. this._responseText = ''; // 存储响应文本
  192. this._responseURL = ''; // 存储响应 URL
  193. this._responseHeaders = null; // 存储响应头
  194. }
  195.  
  196. get open() {
  197. return this._open;
  198. }
  199. set open(value) {}
  200.  
  201. _open(method, url, async = true, user = null, password = null) {
  202. this._method = method;
  203. this._url = url;
  204. this._async = async;
  205. this._user = user;
  206. this._password = password;
  207. this._readyState = XMLHttpRequest.OPENED;
  208. }
  209.  
  210. get send() {
  211. return this._send;
  212. }
  213. set send(value) {}
  214. _send(data) {
  215. this._sendData = data;
  216. this._sendRequest();
  217. }
  218.  
  219. _sendRequest() {
  220. const self = this;
  221.  
  222. // 根据 responseType 设置 fetch 的返回类型
  223. const fetchOptions = {
  224. method: this._method,
  225. headers: new Headers(this._headers),
  226. credentials: this.withCredentials ? 'include' : 'same-origin',
  227. body: this._sendData || undefined,
  228. };
  229.  
  230. // 发送 fetch 请求
  231. return unsafeWindow
  232. .fetch(this._url, fetchOptions)
  233. .then((response) => {
  234. self._response = response;
  235. self._status = response.status;
  236. self._statusText = response.statusText;
  237. self._responseHeaders = response.headers;
  238. self._readyState = XMLHttpRequest.DONE;
  239. self._responseURL = self._url;
  240. const responseType = self._responseType || 'text';
  241. // 设置响应类型
  242. switch (responseType) {
  243. case 'json':
  244. return response.json().then((json) => {
  245. self._responseText = JSON.stringify(json);
  246. self._response = json;
  247. self._onreadystatechange && self._onreadystatechange();
  248. self._onload && self._onload();
  249. self._onloadend && self._onloadend();
  250. });
  251. case 'text':
  252. return response.text().then((text) => {
  253. self._responseText = text;
  254. self._response = text;
  255. self._onreadystatechange && self._onreadystatechange();
  256. self._onload && self._onload();
  257. self._onloadend && self._onloadend();
  258. });
  259. case 'blob':
  260. return response.blob().then((blob) => {
  261. self._response = blob;
  262. self._onreadystatechange && self._onreadystatechange();
  263. self._onload && self._onload();
  264. self._onloadend && self._onloadend();
  265. });
  266. }
  267. })
  268. .catch((error) => {
  269. self._readyState = XMLHttpRequest.DONE;
  270. self._status = 0;
  271. self._statusText = 'Network Error';
  272. self._onreadystatechange && self._onreadystatechange();
  273. self._onload && self._onload();
  274. });
  275. }
  276.  
  277. setRequestHeader(name, value) {
  278. this._headers[name] = value;
  279. return this;
  280. }
  281.  
  282. getResponseHeader(name) {
  283. return this._responseHeaders ? this._responseHeaders.get(name) : null;
  284. }
  285.  
  286. getAllResponseHeaders() {
  287. return this._responseHeaders
  288. .entries()
  289. .reduce((result, [name, value]) => {
  290. return result + `${name}: ${value}\r\n`;
  291. }, '');
  292. }
  293.  
  294. set onreadystatechange(callback) {
  295. this._onreadystatechange = callback;
  296. }
  297.  
  298. set onload(callback) {
  299. this._onload = callback;
  300. }
  301.  
  302. set onloadend(callback) {
  303. this._onloadend = callback;
  304. }
  305.  
  306. get readyState() {
  307. return this._readyState;
  308. }
  309.  
  310. set readyState(state) {
  311. this._readyState = state;
  312. }
  313.  
  314. get response() {
  315. return this._response;
  316. }
  317.  
  318. set response(value) {
  319. this._response = value;
  320. }
  321.  
  322. get responseText() {
  323. return this._responseText;
  324. }
  325.  
  326. set responseText(value) {
  327. this._responseText = value;
  328. }
  329.  
  330. get responseURL() {
  331. return this._responseURL;
  332. }
  333.  
  334. set responseURL(value) {
  335. this._responseURL = value;
  336. }
  337.  
  338. get status() {
  339. return this._status;
  340. }
  341.  
  342. set status(value) {
  343. this._status = value;
  344. }
  345.  
  346. get statusText() {
  347. return this._statusText;
  348. }
  349.  
  350. set statusText(value) {
  351. this._statusText = value;
  352. }
  353.  
  354. get responseType() {
  355. return this._responseType;
  356. }
  357.  
  358. set responseType(type) {
  359. this._responseType = type;
  360. }
  361. };
  362. }
  363.  
  364. function downloadCSV(arrayOfData, filename) {
  365. // 处理数据,使其适合 CSV 格式
  366. const csvContent = arrayOfData
  367. .map((row) =>
  368. row.map((cell) => `"${(cell || '').replace(/"/g, '""')}"`).join(',')
  369. )
  370. .join('\n');
  371.  
  372. // 在 CSV 内容前加上 BOM
  373. const bom = '\uFEFF';
  374. const csvContentWithBOM = bom + csvContent;
  375.  
  376. // 将内容转换为 Blob
  377. const blob = new Blob([csvContentWithBOM], {
  378. type: 'text/csv;charset=utf-8;',
  379. });
  380.  
  381. // 创建一个隐藏的可下载链接
  382. const url = URL.createObjectURL(blob);
  383. const link = document.createElement('a');
  384. link.href = url;
  385. link.setAttribute('download', `${filename}.csv`); // 指定文件名
  386. document.body.appendChild(link);
  387. link.click(); // 触发点击事件
  388. document.body.removeChild(link); // 清除链接
  389. URL.revokeObjectURL(url); // 释放 URL 对象
  390. }
  391.  
  392. // ################### 加载前插入样式覆盖
  393. const style = document.createElement('style');
  394. const cssRules = `
  395. .dropdown-submenu--viewmode {
  396. display: none !important;
  397. }
  398. [field=modified] {
  399. display: none !important;
  400. }
  401.  
  402. [data-value=modified] {
  403. display: none !important;
  404. }
  405. [data-value=lastmodify] {
  406. display: none !important;
  407. }
  408.  
  409. [data-grid-field=modified] {
  410. display: none !important;
  411. }
  412.  
  413. [data-field-key=modified] {
  414. display: none !important;
  415. }
  416.  
  417. #Revisions {
  418. display: none !important;
  419. }
  420.  
  421. #ContentModified {
  422. display: none !important;
  423. }
  424.  
  425. [title="最后修改时间"] {
  426. display: none !important;
  427. }
  428.  
  429. .left-tree-bottom__manager-company--wide {
  430. display: none !important;
  431. }
  432.  
  433. .left-tree-narrow .left-tree-bottom__personal--icons > a:nth-child(1) {
  434. display: none !important;
  435. }
  436.  
  437. .data_handover_card {
  438. display: none !important;
  439. }
  440.  
  441. .dtd-select-item-option[label='智能人事'] {
  442. display: none !important;
  443. }
  444.  
  445. .dtd-select-item-option[label='管理组'] {
  446. display: none !important;
  447. }
  448.  
  449. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(2) {
  450. display: none !important;
  451. }
  452.  
  453. .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(3) {
  454. display: none !important;
  455. }
  456.  
  457. .approve-box .pure-form-container .department-field-view {
  458. display: none !important;
  459. }
  460.  
  461. .approve-box .pure-form-container > div:nth-child(2) {
  462. padding: 0 !important;
  463. }
  464. `;
  465. style.appendChild(document.createTextNode(cssRules));
  466. unsafeWindow.document.head.appendChild(style);
  467.  
  468. // ################### 网页内容加载完成立即执行脚本
  469. unsafeWindow.addEventListener('DOMContentLoaded', function () {
  470. // 监听任务右侧基本信息
  471. const taskRightInfoEles =
  472. unsafeWindow.document.querySelectorAll('#ContentModified');
  473. taskRightInfoEles.forEach((element) => {
  474. const parentDiv = element.closest('div.left_3_col');
  475. if (parentDiv) {
  476. parentDiv.style.display = 'none';
  477. }
  478. });
  479. });
  480.  
  481. // ################### 加载完成动态监听
  482. unsafeWindow.addEventListener('load', function () {
  483. registerMutationObserver(
  484. unsafeWindow.document.body,
  485. {
  486. attributes: false,
  487. childList: true,
  488. subtree: true,
  489. },
  490. {
  491. childList: {
  492. addedNodes: [
  493. // 动态文本替换问题
  494. {
  495. filter: (node, _mutation) => {
  496. return node.textContent.includes('最后修改时间');
  497. },
  498. action: (node, _mutation) => {
  499. replaceTextInNode(node, '最后修改时间', '迭代修改时间');
  500. },
  501. },
  502. // 监听动态弹窗 隐藏设置列表字段-最后修改时间左侧
  503. {
  504. filter: (node, _mutation) => {
  505. return (
  506. node.querySelectorAll('input[value=modified]').length > 0
  507. );
  508. },
  509. action: (node, _mutation) => {
  510. node
  511. .querySelectorAll('input[value=modified]')
  512. .forEach((ele) => {
  513. const parentDiv = ele.closest('div.field');
  514. if (parentDiv) {
  515. parentDiv.style.display = 'none';
  516. }
  517. });
  518. },
  519. },
  520. // 监听动态弹窗 隐藏设置列表字段-最后修改时间右侧
  521. {
  522. filter: (node, _mutation) => {
  523. return (
  524. node.querySelectorAll('span[title=最后修改时间]').length > 0
  525. );
  526. },
  527. action: (node, _mutation) => {
  528. node
  529. .querySelectorAll('span[title=最后修改时间]')
  530. .forEach((ele) => {
  531. const parentDiv = ele.closest('div[role=treeitem]');
  532. if (parentDiv) {
  533. parentDiv.style.display = 'none';
  534. }
  535. });
  536. },
  537. },
  538. // 监听企业微信导出按钮
  539. {
  540. filter: (node, _mutation) => {
  541. return node.querySelectorAll('.js_export').length > 0;
  542. },
  543. action: (node, _mutation) => {
  544. function convertTimestampToTime(timestamp) {
  545. // 创建 Date 对象
  546. const date = new Date(timestamp * 1000); // Unix 时间戳是以秒为单位,而 Date 需要毫秒
  547.  
  548. // 获取小时和分钟
  549. const hours = date.getHours();
  550. const minutes = date.getMinutes();
  551.  
  552. // 确定上午还是下午
  553. const amPm = hours >= 12 ? '下午' : '上午';
  554.  
  555. // 返回格式化的字符串
  556. return `${amPm}${hours}:${minutes
  557. .toString()
  558. .padStart(2, '0')}`;
  559. }
  560. node.querySelectorAll('.js_export').forEach((ele) => {
  561. if (ele.dataset.eventListener === 'true') {
  562. return;
  563. }
  564. ele.dataset.eventListener = 'true';
  565. ele.addEventListener('click', async function (event) {
  566. event.preventDefault();
  567. event.stopPropagation();
  568. const response = await unsafeWindow.fetch(
  569. '/wework_admin/getAdminOperationRecord?lang=zh_CN&f=json&ajax=1&timeZoneInfo%5Bzone_offset%5D=-8',
  570. {
  571. headers: {
  572. 'content-type': 'application/x-www-form-urlencoded',
  573. },
  574. body: unsafeWindow.fetchTmpBody,
  575. method: 'POST',
  576. mode: 'cors',
  577. credentials: 'include',
  578. }
  579. );
  580. const responseJson = await response.json();
  581. const excelData = responseJson.data.operloglist.reduce(
  582. (result, current) => {
  583. const typeMapping = {
  584. 9: '新增部门',
  585. 10: '删除部门',
  586. 11: '移动部门',
  587. 13: '删除成员',
  588. 14: '新增成员',
  589. 15: '更改成员信息',
  590. 21: '更改部门信息',
  591. 23: '登录后台',
  592. 25: '发送邀请',
  593. 36: '修改管理组管理员列表',
  594. 35: '修改管理组应用权限',
  595. 34: '修改管理组通讯录权限',
  596. 88: '修改汇报规则',
  597. 120: '导出相关操作记录',
  598. 162: '批量设置成员信息',
  599. };
  600. const optTypeArray = {
  601. 0: '全部',
  602. 3: '成员与部门变更',
  603. 2: '权限管理变更',
  604. 12: '企业信息管理',
  605. 11: '通讯录与聊天管理',
  606. 13: '外部联系人管理',
  607. 8: '应用变更',
  608. 7: '其他',
  609. };
  610. return [
  611. ...result,
  612. [
  613. convertTimestampToTime(current.operatetime),
  614. current.op_name,
  615. optTypeArray[current.type_oper_1],
  616. typeMapping[current.type] || '其他',
  617. current.data,
  618. current.ip,
  619. ],
  620. ];
  621. },
  622. [
  623. [
  624. '时间',
  625. '操作者',
  626. '操作类型',
  627. '操作行为',
  628. '相关数据',
  629. '操作者IP',
  630. ],
  631. ]
  632. );
  633. downloadCSV(excelData, '管理端操作记录');
  634. });
  635. });
  636. },
  637. },
  638. // 监听钉钉首页-部门修改次数
  639. {
  640. filter: (node, _mutation) => {
  641. const spanDoms = Array.from(
  642. node.querySelectorAll('.admin-panel-v2-module span')
  643. );
  644. return (
  645. spanDoms.filter((e) =>
  646. ['近1月部门修改次数'].includes(e.innerText)
  647. ).length > 0 ||
  648. spanDoms.filter((e) => e.innerText.includes('项指标预警'))
  649. .length > 0
  650. );
  651. },
  652. action: (node, _mutation) => {
  653. const spanDoms = Array.from(
  654. node.querySelectorAll('.admin-panel-v2-module span')
  655. );
  656. spanDoms
  657. .filter((e) => ['近1月部门修改次数'].includes(e.innerText))
  658. .forEach((ele) => {
  659. const parentDiv = ele.parentElement.parentElement;
  660. if (parentDiv) {
  661. parentDiv.childNodes[1].childNodes[0].innerText = 1;
  662. parentDiv.childNodes[1].childNodes[3].style.display =
  663. 'none';
  664. }
  665. });
  666. spanDoms
  667. .filter((e) => e.innerText.includes('项指标预警'))
  668. .forEach((ele) => {
  669. const parentDiv = ele.closest('div');
  670. if (parentDiv) {
  671. parentDiv.style.display = 'none';
  672. }
  673. });
  674. },
  675. },
  676. // 监听钉钉工作台-智能人事应用
  677. {
  678. filter: (node, _mutation) => {
  679. return (
  680. Array.from(
  681. document.querySelectorAll(
  682. '.component_microApp_item > div:nth-child(2)'
  683. )
  684. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  685. );
  686. },
  687. action: (node, _mutation) => {
  688. Array.from(
  689. document.querySelectorAll(
  690. '.component_microApp_item > div:nth-child(2)'
  691. )
  692. )
  693. .filter((e) => ['智能人事'].includes(e.innerText))
  694. .forEach((ele) => {
  695. const parentDiv = ele.closest('.component_microApp_item');
  696. if (parentDiv) {
  697. parentDiv.style.display = 'none';
  698. }
  699. });
  700. },
  701. },
  702. // 监听钉钉通讯录菜单-智能人事
  703. {
  704. filter: (node, _mutation) => {
  705. return (
  706. Array.from(
  707. node.querySelectorAll(
  708. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  709. )
  710. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  711. );
  712. },
  713. action: (node, _mutation) => {
  714. Array.from(
  715. node.querySelectorAll(
  716. 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a'
  717. )
  718. )
  719. .filter((e) => ['智能人事'].includes(e.innerText))
  720. .forEach((ele) => {
  721. const parentDiv = ele.closest('li[role=menuitem]');
  722. if (parentDiv) {
  723. parentDiv.style.display = 'none';
  724. }
  725. });
  726. },
  727. },
  728. // 监听钉钉首页-常用应用智能人事
  729. {
  730. filter: (node, _mutation) => {
  731. return (
  732. Array.from(
  733. node.querySelectorAll('#oa-new-index ul button span')
  734. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  735. );
  736. },
  737. action: (node, _mutation) => {
  738. Array.from(
  739. node.querySelectorAll('#oa-new-index ul button span')
  740. )
  741. .filter((e) => ['智能人事'].includes(e.innerText))
  742. .forEach((ele) => {
  743. const parentDiv = ele.closest('li');
  744. if (parentDiv) {
  745. parentDiv.style.display = 'none';
  746. }
  747. });
  748. },
  749. },
  750. // 监听钉钉工作台-智能人事
  751. {
  752. filter: (node, _mutation) => {
  753. return (
  754. Array.from(
  755. node.querySelectorAll(
  756. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  757. )
  758. ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0
  759. );
  760. },
  761. action: (node, _mutation) => {
  762. Array.from(
  763. node.querySelectorAll(
  764. '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name'
  765. )
  766. )
  767. .filter((e) => ['智能人事'].includes(e.innerText))
  768. .forEach((ele) => {
  769. const parentDiv = ele.closest('tr.dtd-table-row');
  770. if (parentDiv) {
  771. parentDiv.style.display = 'none';
  772. }
  773. });
  774. },
  775. },
  776. // 监听钉钉添加管理员-智能人事权限
  777. {
  778. filter: (node, _mutation) => {
  779. return (
  780. Array.from(node.querySelectorAll('[title=智能人事]')).length >
  781. 0
  782. );
  783. },
  784. action: (node, _mutation) => {
  785. Array.from(node.querySelectorAll('[title=智能人事]')).forEach(
  786. (ele) => {
  787. const parentDiv = ele.closest('.dtd-tree-treenode');
  788. if (parentDiv) {
  789. parentDiv.style.display = 'none';
  790. }
  791. }
  792. );
  793. },
  794. },
  795. // 监听钉钉-智能人事花名册成长记录
  796. {
  797. filter: (node, _mutation) => {
  798. return (
  799. Array.from(node.querySelectorAll('.growth-recorder-list'))
  800. .length > 0
  801. );
  802. },
  803. action: (node, _mutation) => {
  804. function isAfterDate(dateString) {
  805. // 将输入的日期字符串转换为 Date 对象
  806. const inputDate = new Date(dateString);
  807.  
  808. // 创建一个表示 2024 年 8 月 12 日的 Date 对象
  809. const august12_2024 = new Date(2024, 7, 11); // 注意:月份是从 0 开始计数的
  810.  
  811. // 比较两个日期
  812. return inputDate > august12_2024;
  813. }
  814. Array.from(
  815. node.querySelectorAll('.growth-recorder-list > li')
  816. ).forEach((ele) => {
  817. const time = ele.querySelector(
  818. '.growth-recorder-time'
  819. ).innerText;
  820. const title = ele.querySelector(
  821. '.growth-recorder-c-title'
  822. ).innerText;
  823.  
  824. if (title.includes('调岗') && isAfterDate(time)) {
  825. ele.style.display = 'none';
  826. }
  827. });
  828. },
  829. },
  830. // 监听钉钉审计日志
  831. {
  832. filter: (node, _mutation) => {
  833. return (
  834. Array.from(
  835. node.querySelectorAll(
  836. '.audit-content tbody tr>td:nth-child(4)>div'
  837. )
  838. ).filter((e) =>
  839. [
  840. '删除部门',
  841. '添加部门',
  842. '部门名称修改',
  843. '微应用修改',
  844. ].includes(e.innerText)
  845. ).length > 0
  846. );
  847. },
  848. action: (node, _mutation) => {
  849. Array.from(
  850. node.querySelectorAll(
  851. '.audit-content tbody tr>td:nth-child(4)>div'
  852. )
  853. )
  854. .filter((e) =>
  855. ['删除部门', '添加部门', '部门名称修改'].includes(
  856. e.innerText
  857. )
  858. )
  859. .forEach((ele) => {
  860. const parentDiv = ele.closest('tr.dtd-table-row');
  861. if (parentDiv) {
  862. parentDiv.style.display = 'none';
  863. }
  864. });
  865.  
  866. Array.from(
  867. node.querySelectorAll(
  868. '.audit-content tbody tr>td:nth-child(4)>div'
  869. )
  870. )
  871. .filter((e) => ['微应用修改'].includes(e.innerText))
  872. .forEach((e) => {
  873. const parentDiv = e.closest('tr.dtd-table-row');
  874. if (
  875. parentDiv &&
  876. ['马浩然', '曹宁'].includes(
  877. parentDiv
  878. .closest('tr.dtd-table-row')
  879. .querySelectorAll('td:nth-child(2)>div>span')[0]
  880. .innerText.trim()
  881. )
  882. ) {
  883. parentDiv.style.display = 'none';
  884. }
  885. });
  886. },
  887. },
  888. // 监听钉钉审计日志-智能人事、管理组
  889. {
  890. filter: (node, _mutation) => {
  891. return (
  892. Array.from(
  893. node.querySelectorAll(
  894. '.audit-content tbody tr>td:nth-child(3)>div'
  895. )
  896. ).filter((e) => ['智能人事', '管理组'].includes(e.innerText))
  897. .length > 0
  898. );
  899. },
  900. action: (node, _mutation) => {
  901. Array.from(
  902. node.querySelectorAll(
  903. '.audit-content tbody tr>td:nth-child(3)>div'
  904. )
  905. )
  906. .filter((e) => ['智能人事', '管理组'].includes(e.innerText))
  907. .forEach((ele) => {
  908. const parentDiv = ele.closest('tr.dtd-table-row');
  909. if (parentDiv) {
  910. parentDiv.style.display = 'none';
  911. }
  912. });
  913. },
  914. },
  915. // 监听钉钉审计日志-导出按钮
  916. {
  917. filter: (node, _mutation) => {
  918. return (
  919. node.querySelectorAll(
  920. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  921. ).length > 0
  922. );
  923. },
  924. action: (node, _mutation) => {
  925. function formatTimestamp(timestamp) {
  926. // 创建 Date 对象
  927. const date = new Date(timestamp);
  928.  
  929. // 获取年、月、日、小时、分钟、秒
  930. const year = date.getFullYear();
  931. const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,所以需要 +1
  932. const day = String(date.getDate()).padStart(2, '0');
  933. const hours = String(date.getHours()).padStart(2, '0');
  934. const minutes = String(date.getMinutes()).padStart(2, '0');
  935. const seconds = String(date.getSeconds()).padStart(2, '0');
  936.  
  937. // 拼接日期和时间
  938. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  939. }
  940. node
  941. .querySelectorAll(
  942. '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button'
  943. )
  944. .forEach((ele) => {
  945. if (ele.dataset.eventListener === 'true') {
  946. return;
  947. }
  948. ele.dataset.eventListener = 'true';
  949. ele.addEventListener('click', async function (event) {
  950. event.preventDefault();
  951. event.stopPropagation();
  952. function getAllCookies() {
  953. const cookiesArray = document.cookie.split('; ');
  954. const cookiesObj = {};
  955. cookiesArray.forEach((cookie) => {
  956. const parts = cookie.split('=');
  957. cookiesObj[parts[0]] = decodeURIComponent(parts[1]);
  958. });
  959. return cookiesObj;
  960. }
  961. const response = await unsafeWindow.fetch(
  962. '/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',
  963. {
  964. headers: {
  965. accept: 'application/json, text/plain, */*',
  966. 'x-csrf-token': getAllCookies()['csrf_token'],
  967. },
  968. body: null,
  969. method: 'GET',
  970. mode: 'cors',
  971. credentials: 'include',
  972. }
  973. );
  974. const responseJson = await response.json();
  975. responseJson.result = responseJson.result.filter(
  976. (currentData) => {
  977. if (
  978. ['删除部门', '添加部门', '部门名称修改'].includes(
  979. currentData.type.categoryValue
  980. )
  981. ) {
  982. return false;
  983. }
  984. if (
  985. ['微应用修改'].includes(
  986. currentData.type.categoryValue
  987. ) &&
  988. currentData.opName === '马浩然'
  989. ) {
  990. return false;
  991. }
  992. if (
  993. ['智能人事', '管理组'].includes(
  994. currentData.object.categoryValue
  995. )
  996. ) {
  997. return false;
  998. }
  999. return true;
  1000. }
  1001. );
  1002. const excelData = responseJson.result.reduce(
  1003. (result, current) => {
  1004. return [
  1005. ...result,
  1006. [
  1007. formatTimestamp(current.opTime),
  1008. current.opName,
  1009. current.object.categoryValue,
  1010. current.type.categoryValue,
  1011. current.content || '',
  1012. ],
  1013. ];
  1014. },
  1015. [['时间', '操作者', '事件对象', '事件类型', '详细数据']]
  1016. );
  1017. downloadCSV(excelData, '审计日志信息');
  1018. });
  1019. });
  1020. },
  1021. },
  1022. ],
  1023. },
  1024. }
  1025. );
  1026. });
  1027.  
  1028. // ################### 替换请求
  1029. if (
  1030. unsafeWindow.location.pathname.startsWith('/wework_admin') &&
  1031. !unsafeWindow.location.href.includes('loginpage_wx')
  1032. ) {
  1033. registerFetchModifier([
  1034. {
  1035. test: (url, options) => {
  1036. return url.includes('/wework_admin/getAdminOperationRecord');
  1037. },
  1038. prerequest: (url, options) => {
  1039. options.body = options.body
  1040. .split('&')
  1041. .reduce((result, current) => {
  1042. let [key, value] = current.split('=');
  1043. if (key === 'limit') {
  1044. value = 500;
  1045. }
  1046. return [...result, `${key}=${value}`];
  1047. }, [])
  1048. .join('&');
  1049. unsafeWindow.fetchTmpBody = options.body;
  1050. return [url, options];
  1051. },
  1052. preresponse: async (responsePromise) => {
  1053. const response = await responsePromise;
  1054. let responseJson = await response.json();
  1055. responseJson.data.operloglist = responseJson.data.operloglist.filter(
  1056. (e) => e.type_oper_1 !== 3
  1057. );
  1058. responseJson.data.total = responseJson.data.operloglist.length;
  1059. return new Response(JSON.stringify(responseJson), {
  1060. headers: response.headers,
  1061. ok: response.ok,
  1062. redirected: response.redirected,
  1063. status: response.status,
  1064. statusText: response.statusText,
  1065. type: response.type,
  1066. url: response.url,
  1067. });
  1068. },
  1069. },
  1070. ]);
  1071. registerXMLHttpRequestPolyfill();
  1072. }
  1073. // registerXMLHttpRequestPolyfill();
  1074. })();