QQ Mail File Share

qq邮箱中转站文件分享,秒传原理

  1. // ==UserScript==
  2. // @name QQ Mail File Share
  3. // @namespace undefined
  4. // @version 0.0.3
  5. // @description qq邮箱中转站文件分享,秒传原理
  6. // @author https://github.com/cong99
  7. // @match *://mail.qq.com/*
  8. // @match *://cong99.gitee.io/qq_mail_file_share/*
  9. // @require https://code.jquery.com/jquery-latest.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/bootpag/1.0.7/jquery.bootpag.min.js
  11. // @run-at document-start
  12. // @grant unsafeWindow
  13. // @grant GM_setClipboard
  14. // @grant GM_xmlhttpRequest
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict'
  19. var ConstantValue = {
  20. current_version: '0.0.3',
  21. param_start_key: '#QQ_FILE_SHARE_',
  22. param_start_action_key: '#QQ_FILE_ACTION_',
  23. tip_page_host: 'cong99.gitee.io',
  24. tip_page_pathname: '/qq_mail_file_share/',
  25. tip_page_url: 'https://cong99.gitee.io/qq_mail_file_share/',
  26. main_page_pathname: '/cgi-bin/frame_html',
  27. login_page_pathname: '/cgi-bin/login',
  28. script_page_url: 'https://gf.qytechs.cn/zh-CN/scripts/392885',
  29. share_link_url: 'https://mail.qq.com/',
  30. login_info_api_url: 'https://mail.qq.com/cgi-bin/login?fun=psaread&rand=&delegate_url=&target=',
  31. file_list_api_url: 'https://mail.qq.com/cgi-bin/ftnExs_files?t=ftn.json&s=list&ef=js&listtype=self&up=down&sorttype=createtime',
  32. file_list_page_size: 10,
  33. alert_param_error: '分享链接中所带参数错误',
  34. alert_unlogin_error: '请登录(不可用)qq邮箱之后再重新点击分享链接',
  35. alert_get_file_list_error: '获取中转站文件列表失败',
  36. alert_create_file_error: '创建分享文件失败',
  37. tip_jump_error: '如果页面没有主动跳转, 点击这里=>',
  38. tip_jump_update_error: '您的油猴脚本版本过低, 请及时更新=>'
  39. }
  40.  
  41. var $ = $ || window.$
  42. var encodeBase64 = btoa || window.btoa
  43. var decodeBase64 = atob || window.atob
  44.  
  45. $(function(){
  46. if (isTipPage()) {
  47. var supportVerison = ($('#support-min-version').text() || '').trim()
  48. if (isLowerVerison(ConstantValue.current_version, supportVerison)) {
  49. var tipALink = `<a href="${ConstantValue.script_page_url}" target="_blank">脚本地址</a>`
  50. var tipDom = $('#page-tip').html(ConstantValue.tip_jump_update_error + tipALink)
  51. } else {
  52. if (location.hash && location.hash.indexOf(ConstantValue.param_start_key) > -1) {
  53. var qqShareHref = ConstantValue.share_link_url + location.hash
  54. var tipALink = `<a href="${qqShareHref}" target="_blank">下载地址</a>`
  55. var tipDom = $('#page-tip').html(ConstantValue.tip_jump_error + tipALink)
  56. location.href = qqShareHref
  57. }
  58. }
  59. } else {
  60. if (isShareLinkFirstLoad()) {
  61. getLoginRedirect()
  62. } else if (isMainPage()) {
  63. // console.log('shareParam', shareParam)
  64. var shareParam = getShareParam()
  65. var fileShareHelper = new FileShareHelper()
  66. if (shareParam) {
  67. // 秒传
  68. var quickUrl = createQuickUploadUrl(shareParam)
  69.  
  70. request({url: quickUrl}).then(res => {
  71. var responseText = res.response
  72. var json = {}
  73. try {
  74. json = eval(responseText)
  75. } catch(err) {
  76. alert(ConstantValue.alert_create_file_error + ' 失败原因: ' + responseText)
  77. }
  78. if (Number(json.errcode) === 0) {
  79. alert('获取到分享文件: ' + shareParam.sName)
  80. }
  81. }).catch(err => {
  82. // 接口一定返回200, 这里写着以防万一而已
  83. alert(ConstantValue.alert_create_file_error)
  84. }).finally(() => {
  85. // 设置hash为空
  86. location.hash = ''
  87. // 初始化table
  88. fileShareHelper.init(true)
  89. })
  90. } else {
  91. fileShareHelper.init()
  92. }
  93. }
  94. }
  95. })
  96.  
  97. // 原始分享链接 加载
  98. function isShareLinkFirstLoad() {
  99. var hash = (location.hash || '').trim()
  100. if (hash && hash.startsWith(ConstantValue.param_start_key)) {
  101. return true
  102. } else {
  103. return false
  104. }
  105. }
  106.  
  107. // 中途提供跳转的提示页
  108. function isTipPage() {
  109. var pathname = location.pathname
  110. var checkPathName = ConstantValue.tip_page_pathname
  111. return location.host === ConstantValue.tip_page_host && ( pathname === checkPathName || pathname === (checkPathName + 'index') || pathname === (checkPathName + 'index.html'))
  112. }
  113.  
  114. function isMainPage(){
  115. var regx = /sid=/g
  116. return !!location.search.match(regx) && ConstantValue.main_page_pathname === location.pathname
  117. }
  118.  
  119. function getSid() {
  120. return urlArgs()['sid']
  121. }
  122.  
  123. function urlArgs() {
  124. var args = {}
  125. var query = location.search.substring(1)
  126. var pairs = query.split("&")
  127. for (var i = 0; i < pairs.length; i++) {
  128. var pos = pairs[i].indexOf("=")
  129. if (pos == -1) {
  130. continue
  131. }
  132. var name = pairs[i].substring(0, pos)
  133. var value = pairs[i].substring(pos + 1)
  134. args[name] = value
  135. }
  136. return args
  137. }
  138.  
  139. function isLowerVerison(currentVersion, checkVersion) {
  140. return versionToNum(currentVersion) < versionToNum(checkVersion)
  141. }
  142.  
  143. function versionToNum(version) {
  144. if (!version) {
  145. return 0
  146. }
  147. var versionList = version.split('.')
  148. if (versionList.length < 3) {
  149. return 0
  150. } else {
  151. versionList = versionList.map(item => parseInt(item))
  152. return versionList[0] * 100 * 100 + versionList[1] * 100 + versionList[2]
  153. }
  154. }
  155.  
  156. function getShareParam() {
  157. var hash = (location.hash || '').trim()
  158. if (hash && hash.startsWith(ConstantValue.param_start_action_key)) {
  159. var paramStr = hash.substring(ConstantValue.param_start_action_key.length)
  160. var paramJson = {}
  161. try {
  162. var paramJsonStr = decodeBase64(paramStr)
  163. paramJson = JSON.parse(paramJsonStr)
  164. } catch(err) {
  165. alert(ConstantValue.alert_param_error)
  166. return false
  167. }
  168. // 校验参数是否正确
  169. if (paramJson.sName && paramJson.nSize && paramJson.sSHA) {
  170. paramJson.sName = decodeURIComponent(paramJson.sName)
  171. return paramJson
  172. } else {
  173. alert(ConstantValue.alert_param_error)
  174. return false
  175. }
  176. } else {
  177. return false
  178. }
  179. }
  180.  
  181. function getLoginRedirect() {
  182. request({
  183. url: ConstantValue.login_info_api_url
  184. }).then(response => {
  185. // console.log('response', response.finalUrl)
  186. var finalUrl = response.finalUrl || ''
  187. if (finalUrl && finalUrl.indexOf(ConstantValue.main_page_pathname) > -1) {
  188. location.href = response.finalUrl + replaceStartKey(location.hash)
  189. } else {
  190. // 未登录(不可用) https://mail.qq.com/cgi-bin/login?fun=psaread&rand=&delegate_url=&target=
  191. alert(ConstantValue.alert_unlogin_error)
  192. }
  193. })
  194. }
  195.  
  196. function isJsonStr(str) {
  197. if (typeof str == 'string') {
  198. try {
  199. JSON.parse(str)
  200. return true
  201. } catch(e) {
  202. // console.log(e)
  203. return false
  204. }
  205. } else {
  206. return false
  207. }
  208. }
  209.  
  210. function replaceStartKey(hash) {
  211. return hash.replace(ConstantValue.param_start_key, ConstantValue.param_start_action_key)
  212. }
  213.  
  214. function request(option) {
  215. return new Promise((resolve, reject) => {
  216. GM_xmlhttpRequest({
  217. method: option.method || 'GET',
  218. url: option.url,
  219. dataType: option.data_type || 'text',
  220. headers: option.headers || {
  221. },
  222. onload: function (response) {
  223. resolve(response)
  224. },
  225. onerror: function (response) {
  226. reject(response)
  227. }
  228. })
  229. })
  230. }
  231.  
  232. function formatFileSize(fileSize) {
  233. if (fileSize < 1024) {
  234. return fileSize + 'B'
  235. } else if (fileSize < (1024*1024)) {
  236. var temp = fileSize / 1024
  237. temp = temp.toFixed(2)
  238. return temp + 'KB'
  239. } else if (fileSize < (1024*1024*1024)) {
  240. var temp = fileSize / (1024*1024)
  241. temp = temp.toFixed(2)
  242. return temp + 'MB'
  243. } else {
  244. var temp = fileSize / (1024*1024*1024)
  245. temp = temp.toFixed(2)
  246. return temp + 'GB'
  247. }
  248. }
  249.  
  250. function formatTime(timestamp) {
  251. var date = new Date(timestamp * 1000)
  252. var Y = date.getFullYear() + '-'
  253. var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
  254. var D = date.getDate() + ' '
  255. var h = date.getHours() + ':'
  256. var m = date.getMinutes() + ':'
  257. var s = date.getSeconds()
  258. return Y + M + D + h + m + s
  259. }
  260.  
  261. function formatExpireTime(second) {
  262. if (second < 0) {
  263. return '无限期'
  264. } else if (second < 60) {
  265. return second + '秒'
  266. } else if (second < 60 * 60) {
  267. return Math.ceil(second / 60) + '分钟'
  268. } else if (second < 60 * 60 * 24) {
  269. return Math.ceil(second / 60 / 60) + '小时'
  270. } else {
  271. return Math.ceil(second / 60 / 60 / 24) + '天'
  272. }
  273. }
  274.  
  275. function getDownloadUrl(sid, fileItem) {
  276. return `https://mail.qq.com/cgi-bin/ftnDownload302?sid=${sid}&fid=${fileItem.sFileId}&code=${fileItem.sFetchCode}&k=${fileItem.sKey}`
  277. }
  278.  
  279. function getShareLink(fileItem) {
  280. var paramJson = {}
  281. paramJson.sName = encodeURIComponent(fileItem.sName)
  282. paramJson.nSize = fileItem.nSize
  283. paramJson.sSHA = fileItem.sSHA
  284. return ConstantValue.tip_page_url + ConstantValue.param_start_key + encodeBase64(JSON.stringify(paramJson))
  285. }
  286.  
  287. function createQuickUploadUrl(fileItem) {
  288. var sid = getSid()
  289. return `https://mail.qq.com/cgi-bin/ftnCreatefile?uin=&ef=js&resp_charset=UTF8&s=ftnCreate&sid=${sid}&dirid=&path=${fileItem.sName}&size=${fileItem.nSize}&sha=${fileItem.sSHA}&sha3=&appid=2&loc=ftnCreatefile,ftnCreatefile,ftnCreate,ftn2`
  290. }
  291.  
  292. function FileShareHelper(){
  293. this.init = function(showTable) {
  294. insertPurecss()
  295. var listPageDiv = createFileListPage()
  296. var switchBtn = createSwitchBtn(listPageDiv)
  297. createToolDom()
  298. if (showTable) {
  299. switchBtn.click()
  300. }
  301. }
  302. function insertPurecss() {
  303. var myCssStr = `
  304. .button-success,
  305. .button-error,
  306. .button-warning,
  307. .button-secondary {
  308. color: white!important;
  309. border-radius: 4px;
  310. text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
  311. }
  312. .button-success {background: rgb(28, 184, 65)}
  313. .button-error {background: rgb(202, 60, 60)}
  314. .button-warning {background: rgb(223, 117, 20)}
  315. .button-secondary {background: rgb(66, 184, 221)}
  316. .pure-table th {overflow: hidden;text-overflow: ellipsis;white-space: nowrap}
  317. .w80 {width: 80px; margin: auto;}
  318. .w120 {width: 120px; margin: auto;}
  319. .w160 {width: 160px; margin: auto;}
  320. .w240 {width: 240px; margin: auto;}
  321. .w360 {width: 3600px; margin: auto;}
  322. .bootpag li {display: inline-block;}
  323. .bootpag a {display: inline-block; height: 35px; width: 35px; background: #FFF; line-height: 35px; margin: 0 5px; font-weight: bold;}
  324. .bootpag .active a {
  325. background: rgb(202, 60, 60);
  326. color: #Fff;
  327. }
  328. .modal-mask {
  329. position: fixed;
  330. top: 0;
  331. left: 0;
  332. height: 100%;
  333. width: 100%;
  334. z-index: 100;
  335. background: #333;
  336. opacity: 0.5;
  337. }
  338. .modal-dialog {
  339. position: fixed;
  340. top: 25%;
  341. left: 33%;
  342. z-index: 101;
  343. height: 130px;
  344. width: 500px;
  345. background: #fff;
  346. border: 1px solid rgba(0,0,0,.2);
  347. border-radius: 6px;
  348. padding: 20px;
  349. }
  350. .modal-dialog-title {
  351. font-size: 16px;
  352. line-height: 16px;
  353. font-weight: bold;
  354. width: 460px;
  355. overflow: hidden;
  356. text-overflow: ellipsis;
  357. white-space: nowrap;
  358. }
  359. .modal-dialog-content {
  360. height: 80px;
  361. line-height: 14px;
  362. font-size: 14px;
  363. margin: 10px 0;
  364. max-width: 100%;
  365. word-break: break-all;
  366. overflow-y: auto;
  367. }
  368. .modal-dialog-tip {
  369. color: rgb(28, 184, 65);
  370. }
  371. .modal-dialog-close-btn {
  372. float: right;
  373. }
  374. `
  375. var $pureCss = $(`<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css">`)
  376. var $myCss = $(`<style type="text/css">${myCssStr}</style>`)
  377. $('head').append($pureCss)
  378. $('head').append($myCss)
  379. }
  380.  
  381. function createToolDom() {
  382. var $modalMask = $(`<div id="modal-mask" class="modal-mask"></div>`)
  383. var $modalDialog = $(`<div id="modal-dialog" class="modal-dialog">
  384. <h3 id="modal-dialog-title" class="modal-dialog-title">title</h3>
  385. <div id="modal-dialog-content" class="modal-dialog-content">content</div>
  386. <span class="modal-dialog-tip">已将链接复制到剪贴板!</span>
  387. <button id="modal-dialog-close-btn" class="pure-button button-error modal-dialog-close-btn">关闭</button></div>`)
  388. $('body').append($modalMask)
  389. $('body').append($modalDialog)
  390. $('#modal-mask').hide()
  391. $('#modal-dialog').hide()
  392. $('#modal-dialog-close-btn').click(function() {
  393. $('#modal-mask').hide()
  394. $('#modal-dialog').hide()
  395. })
  396. }
  397.  
  398. function openDialog(title, content) {
  399. $('#modal-dialog-title').html(title)
  400. $('#modal-dialog-title').attr('title', title)
  401. $('#modal-dialog-content').html(content)
  402. $('#modal-mask').show()
  403. $('#modal-dialog').show()
  404. }
  405.  
  406. function tipInfo(content, type, duration) {
  407. setTimeout(() => {
  408. // close
  409. }, duration || 1000)
  410. }
  411.  
  412. function createSwitchBtn(listPageDiv) {
  413. var switchToList = '切换至分享列表'
  414. var switchToOrigin = '切换至原页面'
  415. var $switchBtn = $(`<button class="pure-button button-error" style="position: fixed;top: 2px;right: 200px;z-index: 99">${switchToList}</button>`)
  416. $switchBtn.click(function() {
  417. if ($switchBtn.text() === switchToList) {
  418. listPageDiv.show()
  419. $switchBtn.text(switchToOrigin)
  420. } else {
  421. listPageDiv.hide()
  422. $switchBtn.text(switchToList)
  423. }
  424. })
  425. $('body').append($switchBtn)
  426. return $switchBtn
  427. }
  428.  
  429. function createFileListPage() {
  430. var $pageDiv = $('<div style="position: fixed;height: 100%;width: 100%;z-index: 98;top: 0;padding-top: 50px; background: #333"></div>')
  431. $pageDiv.hide()
  432. var $contentWraperDiv = $('<div style="width: 80%; margin: auto;"></div>')
  433. var $refreashBtn = $(`<button class="pure-button button-secondary">刷新[上传过文件之后建议刷新]</button>`)
  434. var $emptyDiv = $(`<div style="text-align: center; color:#fff;"><h1>中转站暂无数据...</h1></div>`)
  435. $emptyDiv.hide()
  436. var $paginationDiv = $(`<div style="text-align: center; margin-top: 25px;"></div>`)
  437. var $fileTable = $(`<table class="pure-table pure-table-horizontal" style="width: 100%; margin: 10px auto; background: #fff"></table>`)
  438. var $tableHead = $(`<thead style="text-align: center"><tr><th>文件名</th><th>文件大小</th><th>上传时间</th><th>过期时间</th><th>下载次数</th><th>操作</th></tr></thead>`)
  439. var $tableBody = $(`<tbody></tbody>`)
  440. $refreashBtn.click(function () {
  441. // 这边有问题, 分页组件要注销然后再重建---->当前没有好办法注销,所以隐藏该按钮
  442. // firstLoadData($paginationDiv, $emptyDiv, $tableBody)
  443. })
  444.  
  445. $fileTable.append($tableHead)
  446. $fileTable.append($tableBody)
  447.  
  448. // $contentWraperDiv.append($refreashBtn)
  449. $contentWraperDiv.append($fileTable)
  450. $contentWraperDiv.append($emptyDiv)
  451. $contentWraperDiv.append($paginationDiv)
  452.  
  453. $pageDiv.append($contentWraperDiv)
  454.  
  455. $('body').append($pageDiv)
  456. firstLoadData($paginationDiv, $emptyDiv, $tableBody)
  457. return $pageDiv
  458. }
  459.  
  460. function insertFileListIntoTable(tableBodyDom, fileList) {
  461. var sid = getSid()
  462. var fileTrList = fileList.map(fileItem => {
  463. return `<tr><th><div class="w120" title="${fileItem.sName}">${fileItem.sName}</div></th>
  464. <th><div class="w80">${formatFileSize(fileItem.nSize)}</div></th>
  465. <th><div class="w160">${formatTime(fileItem.nCreateTime)}</div></th>
  466. <th><div class="w80">${formatExpireTime(fileItem.nExpireTime)}</div></th>
  467. <th><div class="w120">${fileItem.nDownCnt}</div></th>
  468. <th><div class="w240">
  469. <button class="button-warning pure-button share-link-btn" filename="${fileItem.sName}" link="${getShareLink(fileItem)}">分享链接</button>
  470. <a class="button-success pure-button" href="${getDownloadUrl(sid, fileItem)}" target="_blank">下载</a>
  471. </div></th>
  472. </tr>`
  473. })
  474. tableBodyDom.html('')
  475. tableBodyDom.append($(fileTrList.join('')))
  476. $('.share-link-btn').click(function() {
  477. var title = $(this).attr('filename') + ' 分享地址:'
  478. var content = $(this).attr('link')
  479. GM_setClipboard(content, 'text')
  480. openDialog(title, content)
  481. })
  482. }
  483.  
  484. function firstLoadData(paginationDivDom, emptyDivDom, tableBodyDom) {
  485. // 初始获取第一页,创建分页组件
  486. window.currentPageNum = 1
  487. requestFileList(window.currentPageNum, tableBodyDom).then(listRes => {
  488. // 处理dom
  489. var total = listRes.nTotal || 0
  490. total = parseInt(total)
  491. if (total) {
  492. createPagination(paginationDivDom, total, tableBodyDom)
  493. } else {
  494. paginationDivDom.hide()
  495. emptyDivDom.show()
  496. }
  497. })
  498. }
  499.  
  500. function createPagination(paginationDivDom, total, tableBodyDom) {
  501. var pageSize = ConstantValue.file_list_page_size
  502. var totalPage = total % pageSize === 0 ? total / pageSize : Math.ceil(total / pageSize)
  503. paginationDivDom.bootpag({
  504. total: totalPage,
  505. page: 1,
  506. maxVisible: 5,
  507. leaps: true,
  508. firstLastUse: true
  509. }).on('page', function(event, num){
  510. window.currentPageNum = num
  511. requestFileList(num, tableBodyDom)
  512. })
  513. }
  514.  
  515. function requestFileList(page, tableBodyDom) {
  516. var sid = getSid()
  517. // 接口页码是以0开始的
  518. return request({
  519. url: ConstantValue.file_list_api_url + `&sid=${sid}&page=${page - 1}&pagesize=${ConstantValue.file_list_page_size}`,
  520. data_type: 'json'
  521. }).then(response => {
  522. var responseText = response.responseText
  523. var json = {}
  524. try {
  525. json = eval(responseText)
  526. } catch(err) {
  527. alert(ConstantValue.alert_get_file_list_error)
  528. }
  529. insertFileListIntoTable(tableBodyDom, json.oFiles)
  530. // console.log(json)
  531. return json
  532. })
  533. }
  534. }
  535.  
  536. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址