您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Change webpage content
当前为
// ==UserScript== // @name MonkeyModifier // @namespace https://github.com/JiyuShao/greasyfork-scripts // @version 2024-08-15 // @description Change webpage content // @author Jiyu Shao <[email protected]> // @license MIT // @match *://*/* // @run-at document-start // @grant unsafeWindow // ==/UserScript== (function () { 'use strict'; // ################### common tools function replaceTextInNode(node, originalText, replaceText) { // 如果当前节点是文本节点并且包含 originalText if (node instanceof Text && node.textContent.includes(originalText)) { // 替换文本 node.textContent = node.textContent.replace(originalText, replaceText); } // 如果当前节点有子节点,递归处理每个子节点 if (node.hasChildNodes()) { node.childNodes.forEach((child) => { replaceTextInNode(child, originalText, replaceText); }); } } function registerMutationObserver(node, config = {}, options = {}) { const finalConfig = { attributes: false, childList: true, subtree: true, ...config, }; const finalOptions = { // 元素的属性发生了变化 attributes: options.attributes || [], // 子节点列表发生了变化 childList: { addedNodes: options.childList.addedNodes || [ // { // filter: (node) => {}, // action: (node) => {}, // } ], removedNodes: options.childList.removedNodes || [], }, // 文本节点的内容发生了变化 characterData: options.characterData || [], }; const observer = new MutationObserver((mutationsList, _observer) => { mutationsList.forEach((mutation) => { if (mutation.type === 'attributes') { finalOptions.attributes.forEach(({ filter, action }) => { try { if (filter(mutation.target, mutation)) { action(mutation.target, mutation); } } catch (error) { console.error( 'MutationObserver attributes callback failed:', mutation.target, error ); } }); } if (mutation.type === 'childList') { // 检查是否有新增的元素 mutation.addedNodes.forEach((node) => { finalOptions.childList.addedNodes.forEach(({ filter, action }) => { try { if ( [Node.TEXT_NODE, Node.COMMENT_NODE].includes(node.nodeType) ) { return; } if (filter(node, mutation)) { action(node, mutation); } } catch (error) { console.error( 'MutationObserver childList.addedNodes callback failed:', node, error ); } }); }); // 检查是否有删除元素 mutation.removedNodes.forEach((node) => { finalOptions.childList.removedNodes.forEach((filter, action) => { try { if ( [Node.TEXT_NODE, Node.COMMENT_NODE].includes(node.nodeType) ) { return; } if (filter(node, mutation)) { action(node, mutation); } } catch (error) { console.error( 'MutationObserver childList.removedNodes callback failed:', node, error ); } }); }); } if (mutation.type === 'characterData') { finalOptions.characterData.forEach(({ filter, action }) => { try { if (filter(mutation.target, mutation)) { action(mutation.target, mutation); } } catch (error) { console.error( 'MutationObserver characterData callback failed:', mutation.target, error ); } }); } }); }); observer.observe(node, finalConfig); return observer; } function registerFetchModifier(modifierList) { const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = function (url, options) { let finalUrl = url; let finalOptions = { ...options }; let finalResult = null; const matchedModifierList = modifierList.filter((e) => e.test(finalUrl, finalOptions) ); for (const currentModifier of matchedModifierList) { if (currentModifier.prerequest) { [finalUrl, finalOptions] = currentModifier.prerequest( finalUrl, finalOptions ); } } finalResult = originalFetch(finalUrl, finalOptions); for (const currentModifier of matchedModifierList) { if (currentModifier.preresponse) { finalResult = currentModifier.preresponse(finalResult); } } return finalResult; }; } function registerXMLHttpRequestPolyfill() { // 保存原始的 XMLHttpRequest 构造函数 const originalXMLHttpRequest = unsafeWindow.XMLHttpRequest; // 定义新的 XMLHttpRequest 构造函数 unsafeWindow.XMLHttpRequest = class extends originalXMLHttpRequest { constructor() { super(); this._responseType = ''; // 存储 responseType this._onreadystatechange = null; // 存储 onreadystatechange 函数 this._onload = null; // 存储 onload 函数 this._sendData = null; // 存储 send 方法的数据 this._headers = {}; // 存储请求头 this._method = null; // 存储请求方法 this._url = null; // 存储请求 URL this._async = true; // 存储异步标志 this._user = null; // 存储用户名 this._password = null; // 存储密码 this._readyState = XMLHttpRequest.UNSENT; // 存储 readyState this._status = 0; // 存储状态码 this._statusText = ''; // 存储状态文本 this._response = null; // 存储响应对象 this._responseText = ''; // 存储响应文本 this._responseURL = ''; // 存储响应 URL } open(method, url, async = true, user = null, password = null) { this._method = method; this._url = url; this._async = async; this._user = user; this._password = password; this._readyState = XMLHttpRequest.OPENED; } send(data) { this._sendData = data; this._sendRequest(); } _sendRequest() { const self = this; // 根据 responseType 设置 fetch 的返回类型 let fetchOptions = { method: this._method, headers: new Headers(this._headers), }; // 设置请求体 if (this._sendData !== null) { fetchOptions.body = this._sendData; } if (this.withCredentials) { fetchOptions.credentials = 'include'; } // 发送 fetch 请求 return unsafeWindow .fetch(this._url, fetchOptions) .then((response) => { self._response = response; self._status = response.status; self._statusText = response.statusText; self._readyState = XMLHttpRequest.DONE; self._responseURL = self._url; const responseType = self._responseType || 'text'; // 设置响应类型 switch (responseType) { case 'json': return response.json().then((json) => { self._responseText = JSON.stringify(json); self._response = json; self._onreadystatechange && self._onreadystatechange(); self._onload && self._onload(); }); case 'text': return response.text().then((text) => { self._responseText = text; self._response = text; self._onreadystatechange && self._onreadystatechange(); self._onload && self._onload(); }); case 'blob': return response.blob().then((blob) => { self._response = blob; self._onreadystatechange && self._onreadystatechange(); self._onload && self._onload(); }); } }) .catch((error) => { self._readyState = XMLHttpRequest.DONE; self._status = 0; self._statusText = 'Network Error'; self._onreadystatechange && self._onreadystatechange(); self._onload && self._onload(); }); } setRequestHeader(name, value) { this._headers[name] = value; return this; } getResponseHeader(name) { return this._response && this._response.headers ? this._response.headers.get(name) : null; } getAllResponseHeaders() { return this._response && this._response.headers ? this._response.headers : null; } set onreadystatechange(callback) { this._onreadystatechange = callback; } set onload(callback) { this._onload = callback; } get readyState() { return this._readyState; } set readyState(state) { this._readyState = state; } get response() { return this._response; } set response(value) { this._response = value; } get responseText() { return this._responseText; } set responseText(value) { this._responseText = value; } get responseURL() { return this._responseURL; } set responseURL(value) { this._responseURL = value; } get status() { return this._status; } set status(value) { this._status = value; } get statusText() { return this._statusText; } set statusText(value) { this._statusText = value; } get responseType() { return this._responseType; } set responseType(type) { this._responseType = type; } }; } function downloadCSV(arrayOfData, filename) { // 处理数据,使其适合 CSV 格式 const csvContent = arrayOfData .map((row) => row.map((cell) => `"${(cell || '').replace(/"/g, '""')}"`).join(',') ) .join('\n'); // 在 CSV 内容前加上 BOM const bom = '\uFEFF'; const csvContentWithBOM = bom + csvContent; // 将内容转换为 Blob const blob = new Blob([csvContentWithBOM], { type: 'text/csv;charset=utf-8;', }); // 创建一个隐藏的可下载链接 const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', `${filename}.csv`); // 指定文件名 document.body.appendChild(link); link.click(); // 触发点击事件 document.body.removeChild(link); // 清除链接 URL.revokeObjectURL(url); // 释放 URL 对象 } // ################### 加载前插入样式覆盖 const style = document.createElement('style'); const cssRules = ` .dropdown-submenu--viewmode { display: none !important; } [field=modified] { display: none !important; } [data-value=modified] { display: none !important; } [data-value=lastmodify] { display: none !important; } [data-grid-field=modified] { display: none !important; } [data-field-key=modified] { display: none !important; } #Revisions { display: none !important; } #ContentModified { display: none !important; } [title="最后修改时间"] { display: none !important; } .left-tree-bottom__manager-company--wide { display: none !important; } .left-tree-narrow .left-tree-bottom__personal--icons > a:nth-child(1) { display: none !important; } .data_handover_card { display: none !important; } .dtd-select-item-option[label='智能人事'] { display: none !important; } .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(2) { display: none !important; } .data-manage-bar-actions > div:nth-child(2) > div.ant-space-item:nth-child(3) { display: none !important; } .approve-box .pure-form-container .department-field-view { display: none !important; } .approve-box .pure-form-container > div:nth-child(2) { padding: 0 !important; } `; style.appendChild(document.createTextNode(cssRules)); unsafeWindow.document.head.appendChild(style); // ################### 网页内容加载完成立即执行脚本 unsafeWindow.addEventListener('DOMContentLoaded', function () { // 监听任务右侧基本信息 const taskRightInfoEles = unsafeWindow.document.querySelectorAll('#ContentModified'); taskRightInfoEles.forEach((element) => { const parentDiv = element.closest('div.left_3_col'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }); // ################### 加载完成动态监听 unsafeWindow.addEventListener('load', function () { registerMutationObserver( unsafeWindow.document.body, { attributes: false, childList: true, subtree: true, }, { childList: { addedNodes: [ // 动态文本替换问题 { filter: (node, _mutation) => { return node.textContent.includes('最后修改时间'); }, action: (node, _mutation) => { replaceTextInNode(node, '最后修改时间', '迭代修改时间'); }, }, // 监听动态弹窗 隐藏设置列表字段-最后修改时间左侧 { filter: (node, _mutation) => { return ( node.querySelectorAll && node.querySelectorAll('input[value=modified]').length > 0 ); }, action: (node, _mutation) => { node .querySelectorAll('input[value=modified]') .forEach((ele) => { const parentDiv = ele.closest('div.field'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听动态弹窗 隐藏设置列表字段-最后修改时间右侧 { filter: (node, _mutation) => { return ( node.querySelectorAll && node.querySelectorAll('span[title=最后修改时间]').length > 0 ); }, action: (node, _mutation) => { node .querySelectorAll('span[title=最后修改时间]') .forEach((ele) => { const parentDiv = ele.closest('div[role=treeitem]'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听企业微信导出按钮 { filter: (node, _mutation) => { return ( node.querySelectorAll && node.querySelectorAll('.js_export').length > 0 ); }, action: (node, _mutation) => { function convertTimestampToTime(timestamp) { // 创建 Date 对象 const date = new Date(timestamp * 1000); // Unix 时间戳是以秒为单位,而 Date 需要毫秒 // 获取小时和分钟 const hours = date.getHours(); const minutes = date.getMinutes(); // 确定上午还是下午 const amPm = hours >= 12 ? '下午' : '上午'; // 返回格式化的字符串 return `${amPm}${hours}:${minutes .toString() .padStart(2, '0')}`; } node.querySelectorAll('.js_export').forEach((ele) => { if (ele.dataset.eventListener === 'true') { return; } ele.dataset.eventListener = 'true'; ele.addEventListener('click', async function (event) { event.preventDefault(); event.stopPropagation(); const response = await unsafeWindow.fetch( '/wework_admin/getAdminOperationRecord?lang=zh_CN&f=json&ajax=1&timeZoneInfo%5Bzone_offset%5D=-8', { headers: { 'content-type': 'application/x-www-form-urlencoded', }, body: unsafeWindow.fetchTmpBody, method: 'POST', mode: 'cors', credentials: 'include', } ); const responseJson = await response.json(); const excelData = responseJson.data.operloglist.reduce( (result, current) => { const typeMapping = { 9: '新增部门', 10: '删除部门', 11: '移动部门', 13: '删除成员', 14: '新增成员', 15: '更改成员信息', 21: '更改部门信息', 23: '登录后台', 25: '发送邀请', 36: '修改管理组管理员列表', 35: '修改管理组应用权限', 34: '修改管理组通讯录权限', 88: '修改汇报规则', 120: '导出相关操作记录', 162: '批量设置成员信息', }; const optTypeArray = { 0: '全部', 3: '成员与部门变更', 2: '权限管理变更', 12: '企业信息管理', 11: '通讯录与聊天管理', 13: '外部联系人管理', 8: '应用变更', 7: '其他', }; return [ ...result, [ convertTimestampToTime(current.operatetime), current.op_name, optTypeArray[current.type_oper_1], typeMapping[current.type] || '其他', current.data, current.ip, ], ]; }, [ [ '时间', '操作者', '操作类型', '操作行为', '相关数据', '操作者IP', ], ] ); downloadCSV(excelData, '管理端操作记录'); }); }); }, }, // 监听钉钉首页-部门修改次数 { filter: (node, _mutation) => { const spanDoms = Array.from( node.querySelectorAll('.admin-panel-v2-module span') ); return ( node.querySelectorAll && (spanDoms.filter((e) => ['近1月部门修改次数'].includes(e.innerText) ).length > 0 || spanDoms.filter((e) => e.innerText.includes('项指标预警')) .length > 0) ); }, action: (node, _mutation) => { const spanDoms = Array.from( node.querySelectorAll('.admin-panel-v2-module span') ); spanDoms .filter((e) => ['近1月部门修改次数'].includes(e.innerText)) .forEach((ele) => { const parentDiv = ele.parentElement.parentElement; if (parentDiv) { parentDiv.childNodes[1].childNodes[0].innerText = 1; parentDiv.childNodes[1].childNodes[3].style.display = 'none'; } }); spanDoms .filter((e) => e.innerText.includes('项指标预警')) .forEach((ele) => { const parentDiv = ele.closest('div'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉通讯录菜单-智能人事 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from( node.querySelectorAll( 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a' ) ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0 ); }, action: (node, _mutation) => { Array.from( node.querySelectorAll( 'ul.oa-main-ant-menu .oa-main-ant-menu-title-content a' ) ) .filter((e) => ['智能人事'].includes(e.innerText)) .forEach((ele) => { const parentDiv = ele.closest('li[role=menuitem]'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉首页-常用应用智能人事 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from( node.querySelectorAll('#oa-new-index ul button span') ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0 ); }, action: (node, _mutation) => { Array.from( node.querySelectorAll('#oa-new-index ul button span') ) .filter((e) => ['智能人事'].includes(e.innerText)) .forEach((ele) => { const parentDiv = ele.closest('li'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉工作台-智能人事 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from( node.querySelectorAll( '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name' ) ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0 ); }, action: (node, _mutation) => { Array.from( node.querySelectorAll( '.oa-appcenter-app tbody tr>td:nth-child(1)>div .app-name' ) ) .filter((e) => ['智能人事'].includes(e.innerText)) .forEach((ele) => { const parentDiv = ele.closest('tr.dtd-table-row'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉添加管理员-智能人事权限 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from(node.querySelectorAll('[title=智能人事]')).length > 0 ); }, action: (node, _mutation) => { Array.from(node.querySelectorAll('[title=智能人事]')).forEach( (ele) => { const parentDiv = ele.closest('.dtd-tree-treenode'); if (parentDiv) { parentDiv.style.display = 'none'; } } ); }, }, // 监听钉钉审计日志 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from( node.querySelectorAll( '.audit-content tbody tr>td:nth-child(4)>div' ) ).filter((e) => [ '删除部门', '添加部门', '部门名称修改', '微应用修改', ].includes(e.innerText) ).length > 0 ); }, action: (node, _mutation) => { Array.from( node.querySelectorAll( '.audit-content tbody tr>td:nth-child(4)>div' ) ) .filter((e) => ['删除部门', '添加部门', '部门名称修改'].includes( e.innerText ) ) .forEach((ele) => { const parentDiv = ele.closest('tr.dtd-table-row'); if (parentDiv) { parentDiv.style.display = 'none'; } }); Array.from( node.querySelectorAll( '.audit-content tbody tr>td:nth-child(4)>div' ) ) .filter((e) => ['微应用修改'].includes(e.innerText)) .forEach((e) => { const parentDiv = e.closest('tr.dtd-table-row'); if ( parentDiv && parentDiv .closest('tr.dtd-table-row') .querySelectorAll('td:nth-child(2)>div>span')[0] .innerText.includes('马浩然') ) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉审计日志-智能人事 { filter: (node, _mutation) => { return ( node.querySelectorAll && Array.from( node.querySelectorAll( '.audit-content tbody tr>td:nth-child(3)>div' ) ).filter((e) => ['智能人事'].includes(e.innerText)).length > 0 ); }, action: (node, _mutation) => { Array.from( node.querySelectorAll( '.audit-content tbody tr>td:nth-child(3)>div' ) ) .filter((e) => ['智能人事'].includes(e.innerText)) .forEach((ele) => { const parentDiv = ele.closest('tr.dtd-table-row'); if (parentDiv) { parentDiv.style.display = 'none'; } }); }, }, // 监听钉钉审计日志-导出按钮 { filter: (node, _mutation) => { return ( node.querySelectorAll && node.querySelectorAll( '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button' ).length > 0 ); }, action: (node, _mutation) => { function formatTimestamp(timestamp) { // 创建 Date 对象 const date = new Date(timestamp); // 获取年、月、日、小时、分钟、秒 const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,所以需要 +1 const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); // 拼接日期和时间 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } node .querySelectorAll( '.audit-content .dd-toolbar-btns-container .dd-toolbar-action-btns > div:nth-child(2) button' ) .forEach((ele) => { if (ele.dataset.eventListener === 'true') { return; } ele.dataset.eventListener = 'true'; ele.addEventListener('click', async function (event) { event.preventDefault(); event.stopPropagation(); const response = await unsafeWindow.fetch( '/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×tamp=1723622438315', { headers: { accept: 'application/json, text/plain, */*', 'x-csrf-token': 'aXe7U6eGmTgUbnTStnqSU8', }, body: null, method: 'GET', mode: 'cors', credentials: 'include', } ); const responseJson = await response.json(); responseJson.result = responseJson.result.filter( (currentData) => { if ( ['删除部门', '添加部门', '部门名称修改'].includes( currentData.type.categoryValue ) ) { return false; } if ( ['微应用修改'].includes( currentData.type.categoryValue ) && currentData.opName === '马浩然' ) { return false; } if ( ['智能人事'].includes( currentData.object.categoryValue ) ) { return false; } return true; } ); const excelData = responseJson.result.reduce( (result, current) => { return [ ...result, [ formatTimestamp(current.opTime), current.opName, current.object.categoryValue, current.type.categoryValue, current.content || '', ], ]; }, [['时间', '操作者', '事件对象', '事件类型', '详细数据']] ); downloadCSV(excelData, '审计日志信息'); }); }); }, }, ], }, } ); }); // ################### 替换请求 if ( unsafeWindow.location.pathname.startsWith('/wework_admin') && !unsafeWindow.location.href.includes('loginpage_wx') ) { registerFetchModifier([ { test: (url, options) => { return url.includes('/wework_admin/getAdminOperationRecord'); }, prerequest: (url, options) => { options.body = options.body .split('&') .reduce((result, current) => { let [key, value] = current.split('='); if (key === 'limit') { value = 500; } return [...result, `${key}=${value}`]; }, []) .join('&'); unsafeWindow.fetchTmpBody = options.body; return [url, options]; }, preresponse: async (responsePromise) => { const response = await responsePromise; let responseJson = await response.json(); responseJson.data.operloglist = responseJson.data.operloglist.filter( (e) => e.type_oper_1 !== 3 ); responseJson.data.total = responseJson.data.operloglist.length; return new Response(JSON.stringify(responseJson), { headers: response.headers, ok: response.ok, redirected: response.redirected, status: response.status, statusText: response.statusText, type: response.type, url: response.url, }); }, }, ]); registerXMLHttpRequestPolyfill(); } // registerXMLHttpRequestPolyfill(); })();