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