腾讯文档助手(批量导出)

批量导出

  1. // ==UserScript==
  2. // @name 腾讯文档助手(批量导出)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-02-11
  5. // @description 批量导出
  6. // @author zhengqinqian
  7. // @match https://doc.weixin.qq.com/home/recent
  8. // @match https://doc.weixin.qq.com/home/mydoc
  9. // @match http://doc.weixin.qq.com/home/recent
  10. // @require https://cdn.jsdelivr.net/npm/toastr@2.1.4/toastr.min.js
  11. // @resource toastrCss https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=qq.com
  13. // @grant GM_cookie
  14. // @license MIT
  15. // @grant GM_xmlhttpRequest
  16. // ==/UserScript==
  17. //本地vue存储的文档信息
  18. let list_data;
  19. //doc下载API
  20. let doc_export_api="https://doc.weixin.qq.com/v1/export/export_office?sid="
  21. //doc导出进度查询
  22. let doc_export_query_api="https://doc.weixin.qq.com/v1/export/query_progress?"
  23. var localHref = window.location.href;
  24. var UA = navigator.userAgent;
  25. //正在导出的作业
  26. var exporting_operationId=[]
  27. var docCookies = {
  28. getItem: function (sKey) {
  29. return (
  30. decodeURIComponent(
  31. document.cookie.replace(
  32. new RegExp(
  33. "(?:(?:^|.*;)\\s*" +
  34. encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
  35. "\\s*\\=\\s*([^;]*).*$)|^.*$",
  36. ),
  37. "$1",
  38. ),
  39. ) || null
  40. );
  41. },
  42. setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
  43. if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
  44. return false;
  45. }
  46. var sExpires = "";
  47. if (vEnd) {
  48. switch (vEnd.constructor) {
  49. case Number:
  50. sExpires =
  51. vEnd === Infinity
  52. ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
  53. : "; max-age=" + vEnd;
  54. break;
  55. case String:
  56. sExpires = "; expires=" + vEnd;
  57. break;
  58. case Date:
  59. sExpires = "; expires=" + vEnd.toUTCString();
  60. break;
  61. }
  62. }
  63. document.cookie =
  64. encodeURIComponent(sKey) +
  65. "=" +
  66. encodeURIComponent(sValue) +
  67. sExpires +
  68. (sDomain ? "; domain=" + sDomain : "") +
  69. (sPath ? "; path=" + sPath : "") +
  70. (bSecure ? "; secure" : "");
  71. return true;
  72. },
  73. removeItem: function (sKey, sPath, sDomain) {
  74. if (!sKey || !this.hasItem(sKey)) {
  75. return false;
  76. }
  77. document.cookie =
  78. encodeURIComponent(sKey) +
  79. "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" +
  80. (sDomain ? "; domain=" + sDomain : "") +
  81. (sPath ? "; path=" + sPath : "");
  82. return true;
  83. },
  84. hasItem: function (sKey) {
  85. return new RegExp(
  86. "(?:^|;\\s*)" +
  87. encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") +
  88. "\\s*\\=",
  89. ).test(document.cookie);
  90. },
  91. keys: /* optional method: you can safely remove it! */ function () {
  92. var aKeys = document.cookie
  93. .replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "")
  94. .split(/\s*(?:\=[^;]*)?;\s*/);
  95. for (var nIdx = 0; nIdx < aKeys.length; nIdx++) {
  96. aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
  97. }
  98. return aKeys;
  99. },
  100. };
  101.  
  102. //vue数据初始化
  103. function setData(){
  104. list_data=document.querySelector(".fileList_item_wrapper").__vue__.$attrs.list;
  105. console.log(list_data.length)
  106. }
  107. //恢复console.log
  108. function setConsole() {
  109. // var iframe = document.createElement('iframe');
  110. // iframe.style.display = 'none';
  111. // document.body.appendChild(iframe);
  112. // var console = iframe.contentWindow.console;
  113. // window.console = console;
  114. // window.__autoClearConsoleTimer && clearInterval(window.__autoClearConsoleTimer)
  115. setInterval(()=>{window.__autoClearConsoleTimer && clearInterval(window.__autoClearConsoleTimer)},3000)
  116. // window.__resetConsole()
  117. // setTimeout(window.__resetConsole(),1000)
  118. //清除定时器
  119. // clearInterval(window.__autoClearConsoleTimer)
  120. }
  121. function JSON_to_URLEncoded(element,key,list){
  122. var list = list || [];
  123. if(typeof(element)=='object'){
  124. for (var idx in element)
  125. JSON_to_URLEncoded(element[idx],key?key+'['+idx+']':idx,list);
  126. } else {
  127. list.push(key+'='+encodeURIComponent(element));
  128. }
  129. return list.join('&');
  130. }
  131. function AjaxCall(href,type,data,callback) {
  132. var encode_data="";
  133. if(data){
  134. encode_data=JSON_to_URLEncoded(data);
  135. }
  136. console.log("AjaxCall:"+href+","+type+","+encode_data+","+encode_data.length)
  137. GM_xmlhttpRequest({
  138. method: type,
  139. url: href,
  140. data: encode_data,
  141. headers: {
  142. "User-Agent": UA,
  143. "Origin": "https://doc.weixin.qq.com/home",
  144. "content-type":"application/x-www-form-urlencoded; charset=UTF-8",
  145. // "cookie": document.cookie,
  146. // "Content-Length":encode_data.length,
  147. // "Host":"doc.weixin.qq.com"
  148. },
  149. onload: function(data,status) {
  150. if(data.readyState==4 && data.status==200){
  151. var htmlTxt = data.responseText;
  152. callback(null,htmlTxt);
  153. };
  154. },
  155. onerror: function (error) {
  156. console.info("AjaxCall.onerror")
  157. callback(error);
  158. },
  159. ontimeout: function (error) {
  160. console.info("AjaxCall.ontimeout")
  161. callback(error);
  162. },
  163. });
  164. };
  165. // 下载文档
  166. function downLoadDoc(url,data){
  167. return new Promise(function(reslove,reject){
  168. AjaxCall(url,"POST",data,(error,reponse)=>{
  169. if(error){
  170. console.log("downLoadDoc error")
  171. resolve([false]);
  172. return;
  173. }
  174. var json = JSON.parse(reponse);
  175. if(json.ret==0){
  176. console.log("downLoadDoc return :"+reponse)
  177. console.log(reponse)
  178. reslove([true,json.operationId])
  179. }else{
  180. console.warn("downLoadDoc return :"+reponse)
  181. console.warn(reponse)
  182. reslove([false])
  183. }
  184. })
  185. })
  186. }
  187. //查询进度
  188. function queryProgress(url,itemId){
  189. return new Promise(function(reslove,reject){
  190. AjaxCall(url,"GET",null,(error,reponse)=>{
  191. if(error){
  192. console.log("queryProgress error")
  193. resolve([false,itemId]);
  194. return;
  195. }
  196. var json = JSON.parse(reponse);
  197. if(json.ret==0&&json.progress==100){
  198. console.log("queryProgress ret:"+json.ret)
  199. console.log(reponse)
  200. reslove([true,json.file_url])
  201. }else{
  202. console.warn("queryProgress ret "+json.ret)
  203. console.warn(reponse)
  204. reslove([false,itemId])
  205. }
  206. })
  207. })
  208. }
  209. // selector is optional - defaults to all elements including window and document
  210. // Do not pass window / document objects. Instead use pseudoselectors: 'window' or 'document'
  211. // eTypeArray is optional - defaults to clearing all event types
  212. function removeAllEventListeners(selector = '*', eTypeArray = ['*']) {
  213. switch (selector.toLowerCase()) {
  214. case 'window':
  215. removeListenersFromElem(window);
  216. break;
  217. case 'document':
  218. removeListenersFromElem(document);
  219. break;
  220. case '*':
  221. removeListenersFromElem(window);
  222. removeListenersFromElem(document);
  223. default:
  224. document.querySelectorAll(selector).forEach(removeListenersFromElem);
  225. }
  226. function removeListenersFromElem(elem) {
  227. let eListeners = getEventListeners(elem);
  228. let eTypes = Object.keys(eListeners);
  229. for (let eType of inBoth(eTypes, eTypeArray)) {
  230. eListeners[eType].forEach((eListener)=>{
  231. let options = {};
  232. inBoth(Object.keys(eListener), ['capture', 'once', 'passive', 'signal'])
  233. .forEach((key)=>{ options[key] = eListener[key] });
  234. elem.removeEventListener(eType, eListener.listener, eListener.useCapture);
  235. elem.removeEventListener(eType, eListener.listener, options);
  236. });
  237. }
  238. }
  239. function inBoth(arrA, arrB) {
  240. setB = new Set(arrB);
  241. if (setB.has('*')) {
  242. return arrA;
  243. } else {
  244. return arrA.filter(a => setB.has(a));
  245. }
  246. }
  247. }
  248. //阻止冒泡,去除框外取消勾选 todo
  249. function cancelBubble(){
  250. document.querySelector("body").addEventListener("click",(e)=>{e.stopPropagation();})
  251. document.querySelector("body").addEventListener("mousedown",(e)=>{e.stopPropagation();})
  252. var home_topfile=document.querySelector(".home_topfile");
  253. // removeAllEventListeners(".home_topfile")
  254. // home_topfile.replaceWith(home_topfile.cloneNode(true))
  255. // getEventListeners(document.querySelector(".home_topfile"))
  256. var placeToReplace;
  257. if (window.EventTarget && EventTarget.prototype.addEventListener) {
  258. placeToReplace = EventTarget;
  259. } else {
  260. placeToReplace = Element;
  261. }
  262.  
  263. placeToReplace.prototype.oldaddEventListener = placeToReplace.prototype.addEventListener;
  264. placeToReplace.prototype.addEventListener = function(event, handler, placeholder) {
  265. // console.log("calling substitute");
  266. if (arguments.length < 3) {
  267. this.oldaddEventListener(event, handler, false);
  268. } else {
  269. this.oldaddEventListener(event, handler, placeholder);
  270. }
  271. }
  272. document.querySelector(".home_topfile").addEventListener("mousedown", function() {
  273. console.log("foo");
  274. });
  275.  
  276.  
  277. }
  278. //onLoad
  279. function pageOnload(){
  280. // setData
  281. setData();
  282. // setConsole
  283. setConsole();
  284. // cancelBubble();
  285. //recent页面显示在tab-container
  286. if(localHref.includes("home/recent")){
  287. //批量导出按钮
  288. document.querySelector(".tab-container").insertAdjacentHTML('beforeend','<button id="batchExport" type="button" class="xd_btn fileToolbar_button xd_btn_Blue xd_btn_Supper" style="width: auto; ">批量导出</button>')
  289. //全选按钮
  290. document.querySelector(".tab-container").insertAdjacentHTML('beforeend','<button id="selectAllItem" type="button" class="xd_btn fileToolbar_button xd_btn_Supper" style="width: auto;color: white;background: seagreen;display:inline">全选</button>')
  291. }else{
  292. document.querySelector(".xd-web-header_toolbar").insertAdjacentHTML('beforeend','<button id="selectAllItem" type="button" class="xd_btn fileToolbar_button xd_btn_Supper" style="width: auto;color: white;background: seagreen;display:inline">全选</button>')
  293. document.querySelector(".xd-web-header_toolbar").insertAdjacentHTML('beforeend','<button id="batchExport" type="button" class="xd_btn fileToolbar_button xd_btn_Blue xd_btn_Supper" style="width: auto; ">批量导出</button>')
  294. }
  295. // document.querySelectorAll(".xd_checkbox").forEach(item=>{
  296. // console.log("debug for fileList_item_checkbox")
  297. // parentDiv=item.parentNode.parentNode;
  298. // item.addEventListener("click",(e)=>{
  299. // if(parentDiv.getAttribute("class").indexOf("fileList_item_Active")==-1){
  300. // //勾选
  301. // console.log("click select")
  302. // parentDiv.classList.add("fileList_item_Active");
  303. // item.firstChild.classList.replace("xd_common_unselect-normal","xd_common_select-normal");
  304. // }
  305. // else{
  306. // //取消勾选
  307. // console.log("rever select")
  308. // parentDiv.classList.remove("fileList_item_Active");
  309. // item.firstChild.classList.replace("xd_common_select-normal","xd_common_unselect-normal");
  310. // }
  311. // })
  312. // });
  313. document.querySelectorAll(".fileList_item_checkbox").forEach(item=>{item.style.display="flex";});
  314. //点击批量导出
  315. document.querySelector("#batchExport").addEventListener("click",(e)=>{
  316. console.log("batchExport")
  317. if(list_data.length<=0){
  318. console.warn("data is null")
  319. return
  320. }
  321. var docList=document.querySelectorAll(".fileList_item_wrapper");
  322. console.log(docList.length)
  323. for(var i=0;i<docList.length;i++){
  324. var docItem=docList[i];
  325. console.log("i:"+i+","+docItem.firstChild.className+","+list_data[i].doc_id)
  326. //选中
  327. if(docItem.firstChild.className.includes("fileList_item_Active")){
  328. // if(i>0){
  329. // return;
  330. // }
  331. // 执行导出作业
  332. var url=doc_export_api+docCookies.getItem("wedoc_sid")
  333. console.log("docItem.doc_id:"+list_data[i].doc_id)
  334. downLoadDoc(url,{"docId":list_data[i].doc_id})
  335. .then((resp)=>{
  336. console.log("downLoadDoc then:"+resp)
  337. if(resp[0]){
  338. //加入到待查询列表中
  339. exporting_operationId.push(resp[1])
  340. //清除选中状态,默认会清除
  341. // docItem.firstChild.className.remove("fileList_item_Active")
  342.  
  343. }
  344. })
  345. }
  346. }
  347. })
  348. //点击全选
  349. document.querySelector("#selectAllItem").addEventListener("click",()=>{
  350. //全选
  351. if(document.querySelector(".fileList_item_wrapper > div:first-child").getAttribute("class").indexOf("fileList_item_Active")==-1){
  352. console.log("click selectAll")
  353. document.querySelectorAll(".fileList_item_wrapper > div:first-child").forEach(item=>item.classList.add("fileList_item_Active"));
  354. document.querySelectorAll(".xd_checkbox > i").forEach(item=>item.classList.replace("xd_common_unselect-normal","xd_common_select-normal"));
  355. }else{
  356. //取消全选
  357. console.log("rever selectAll")
  358. document.querySelectorAll(".fileList_item_wrapper > div:first-child").forEach(item=>item.classList.remove("fileList_item_Active"));
  359. document.querySelectorAll(".xd_checkbox > i").forEach(item=>item.classList.replace("xd_common_select-normal","xd_common_unselect-normal"));
  360. }
  361. })
  362. console.log("pageOnload end")
  363. }
  364. let await_flag=false;
  365. (function() {
  366. document.onreadystatechange = function () {
  367. if (document.readyState === "complete") {
  368. setTimeout(()=>pageOnload(),1000)
  369. }
  370. };
  371. //window.addEventListener("load", (event)=>pageOnload(event));
  372. var wedoc_skey=docCookies.getItem("wedoc_skey");
  373. var wedoc_ticket=docCookies.getItem("wedoc_ticket");
  374.  
  375. setInterval(async()=>{
  376. if(await_flag){
  377. console.log("busy")
  378. return;
  379. }
  380. await_flag=true;
  381. var time_stmap=new Date().getTime();
  382. console.log(time_stmap+","+exporting_operationId.length)
  383. var new_exporting_operationId=[];
  384. for(var i=0 ;i<exporting_operationId.length;i++){
  385. var item=exporting_operationId[i];
  386. var url=doc_export_query_api+"operationId="+item+"&timestamp="+new Date().getTime()
  387. await queryProgress(url,item)
  388. .then((resp)=>{
  389. console.log("downLoadDoc retuen:"+resp)
  390. if(resp[0]){
  391. //下载
  392. window.open(resp[1])
  393. }else{
  394. new_exporting_operationId.push(resp[1])
  395. }
  396. })
  397. }
  398. exporting_operationId=new_exporting_operationId;
  399. await_flag=false;
  400. },3000)
  401. // alert("wedoc_skey:"+wedoc_skey+"\n"+"wedoc_ticket"+wedoc_ticket);
  402. })();
  403.