Github Copy Raw File URL and Download File

Add buttons at the end of each file line to copy the raw file URL and download the file

2024-09-03 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name Github Copy Raw File URL and Download File
  3. // @name:zh-CN Github 复制原始文件 URL 与下载文件
  4. // @name:zh-TW Github 複製原始檔案 URL 與下載檔案
  5. // @name:vi Github Sao chép URL tệp gốc và tải xuống tệp
  6. // @name:ko Github 원본 파일 URL 복사 및 파일 다운로드
  7. // @name:ja Github 原始ファイル URL をコピーし、ファイルをダウンロードする
  8. // @name:en Github Copy Raw File URL and Download File
  9. // @name:de Github Rohdatei-URL kopieren und Datei herunterladen
  10. // @description Add buttons at the end of each file line to copy the raw file URL and download the file
  11. // @description:zh-CN 在每个文件行的末尾添加按钮,以复制原始文件 URL 和下载文件
  12. // @description:zh-TW 在每個檔案行的末尾添加按鈕,以複製原始檔案 URL 和下載檔案
  13. // @description:vi Thêm nút vào cuối mỗi dòng tệp để sao chép URL tệp gốc và tải xuống tệp
  14. // @description:ko 각 파일 행 끝에 원본 파일 URL 복사 및 파일 다운로드 버튼 추가
  15. // @description:ja 各ファイル行の末尾に、原始ファイルURLをコピーし、ファイルをダウンロードするボタンを追加
  16. // @description:en Add buttons at the end of each file line to copy the raw file URL and download the file
  17. // @description:de Fügen Sie Schaltflächen am Ende jeder Dateizeile hinzu, um die Rohdatei-URL zu kopieren und die Datei herunterzuladen
  18. // @namespace https://github.com/ChinaGodMan/UserScripts
  19. // @version 2.2.0.11
  20. // @author Kamikaze (https://github.com/Kamiikaze) ,人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  21. // @match https://github.com/*
  22. // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
  23. // @run-at document-ready
  24. // @license MIT
  25. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  26. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  27. // ==/UserScript==
  28.  
  29.  
  30. // Need an Interval to detect path changes on github tree one-pager
  31. // Define the number of seconds
  32. const scanInterval = 2
  33.  
  34.  
  35. const waitForFilelist = setInterval(() => {
  36. let fileListContainer = document.querySelector("div.Box > div.js-details-container.Details div") || document.querySelector("table")
  37. let fileList = []
  38. let isTable = false
  39.  
  40. if (fileListContainer.tBodies) {
  41. fileList = fileListContainer.tBodies[0].children
  42. isTable = true
  43. } else {
  44. fileList = fileListContainer.children
  45. }
  46.  
  47. if (fileList < 1) return
  48.  
  49. appendButtons(fileList, isTable)
  50.  
  51. }, scanInterval * 1000)
  52.  
  53. function appendButtons(fileList, isTable = false) {
  54. let fileUrl = ""
  55. let rawFileUrl = ""
  56. for (let i = 0; i < fileList.length; i++) {
  57. let file = fileList[i]
  58.  
  59. if (file.classList.contains("cp-btn-rdy")) continue
  60.  
  61. file.classList.add("cp-btn-rdy")
  62.  
  63. if (!isTable) {
  64. if (
  65. file.classList.contains("sr-only") ||
  66. file.childElementCount !== 4
  67. ) continue
  68.  
  69. fileUrl = file.querySelector('div:nth-child(2) .js-navigation-open')
  70. .href
  71. } else {
  72. if (i === 0) continue
  73.  
  74. if (
  75. file.classList.contains("sr-only")
  76. ) continue
  77.  
  78.  
  79. fileUrl = file.querySelector("a")
  80. .href
  81. file = file.querySelector("td:nth-child(4) > div")
  82. }
  83. //alert(fileUrl)
  84. // Dont add button if its a folder
  85. if (!fileUrl.includes("/blob/")) continue
  86.  
  87. rawFileUrl = fileUrl.replace('/blob/', '/raw/')
  88. file.style = "display: flex; justify-content: flex-end;"
  89. file.append(creatyCopyButton(rawFileUrl))
  90. file.append(creatyDownButton(rawFileUrl))
  91. }
  92. };
  93.  
  94. function creatyCopyButton(copyText) {
  95. const copy2clipboard = `
  96. <clipboard-copy aria-label="Copy" value="test value" data-view-component="true" class="" tabindex="0" role="button" title="Copy raw file url">
  97. <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy">
  98. <path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path>
  99. </svg>
  100. </clipboard-copy>`
  101.  
  102. const copyButton = document.createElement('div')
  103. copyButton.setAttribute('role', 'gridcell')
  104. copyButton.style = "margin-left: 10px; display: inline;"
  105. copyButton.innerHTML = copy2clipboard
  106. copyButton.children[0].value = copyText
  107. copyButton.children[0].style = "cursor: pointer;"
  108.  
  109. return copyButton
  110. }
  111. function creatyDownButton(copyText) {
  112. const copy2clipboard = `
  113. <clipboard-copy aria-label="Download" value="test value" data-view-component="true" class="" tabindex="0" role="button" title="Download raw file url">
  114. <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-download">
  115. <path fill-rule="evenodd" d="M1.75 14.25A1.75 1.75 0 013.5 12.5h9a1.75 1.75 0 011.75 1.75v1.5a.75.75 0 01-.75.75H2.5a.75.75 0 01-.75-.75v-1.5zM10.75 9.25a.25.25 0 01.25.25v2.5a.25.25 0 01-.25.25H5.25a.25.25 0 01-.25-.25v-2.5a.25.25 0 01.25-.25h5.5zM8 1.75a.25.25 0 01.25.25v7.5a.25.25 0 01-.25.25H6.75a.25.25 0 01-.25-.25v-7.5a.25.25 0 01.25-.25h1.5zM10.25 5.25l1.5 1.5a.25.25 0 01.35 0l3-3a.25.25 0 00-.35-.35L11 5.25 9.25 3.5a.25.25 0 00-.35.35z"></path>
  116. </svg>
  117. </clipboard-copy>
  118. `
  119.  
  120. const copyButton = document.createElement('div')
  121. copyButton.setAttribute('role', 'gridcell')
  122. copyButton.style = "margin-left: 10px; display: inline;"
  123. copyButton.innerHTML = copy2clipboard
  124. copyButton.children[0].value = copyText
  125. copyButton.children[0].style = "cursor: pointer;"
  126. copyButton.addEventListener('click', () => {
  127. // window.location.href = copyText;
  128. downloadFile(copyText, getFilenameFromUrl(copyText))
  129. })
  130. return copyButton
  131. }
  132. function downloadFile(url, filename) {
  133. var xhr = new XMLHttpRequest()
  134. xhr.open('GET', url, true)
  135. xhr.responseType = 'blob'
  136. xhr.onload = function () {
  137. if (xhr.status === 200) {
  138. var blob = xhr.response
  139. var objectUrl = window.URL.createObjectURL(blob)
  140. var a = document.createElement('a')
  141. a.href = objectUrl
  142. a.download = filename // 设置下载文件名
  143. document.body.appendChild(a)
  144. a.click()
  145. window.URL.revokeObjectURL(objectUrl) // 清理 object URL
  146. document.body.removeChild(a) // 清理 DOM
  147. }
  148. }
  149. xhr.send()
  150. }
  151. function getFilenameFromUrl(url) {
  152. if (typeof url !== 'string' || url.trim() === '') {
  153. logMessage('getFilenameFromUrl', 'URL无效,默认文件名download', false)
  154. return 'download' // 返回一个默认的文件名
  155. }
  156. var lastSlashIndex = url.lastIndexOf('/')
  157. if (lastSlashIndex === -1 || lastSlashIndex === url.length - 1) {
  158. logMessage('getFilenameFromUrl', 'URL格式无效缺少文件名,默认文件名download', false)
  159. return 'download' // 返回一个默认的文件名
  160. }
  161. var filenameWithExtension = url.substring(lastSlashIndex + 1)
  162. var decodedFilename = decodeURIComponent(filenameWithExtension)
  163. decodedFilename = decodedFilename.replace(/%20/g, '_') // 替换所有的 %20 为下划线
  164. return decodedFilename
  165. }