Github 仓库大小

在 github 搜索和存储库页面上的存储库名称旁边添加存储库大小

当前为 2024-08-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Github Repo Size+
  3. // @name:zh-CN Github 仓库大小
  4. // @namespace https://github.com/qinwuyuan-cn
  5. // @description Adds the repo size next to the repo name on github search and repo pages
  6. // @description:zh-CN 在 github 搜索和存储库页面上的存储库名称旁边添加存储库大小
  7. // @version 0.1.2.21
  8. // @author mshll & 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  9. // @match *://github.com/search*
  10. // @match *://github.com/*/*
  11. // @match *://github.com/*?tab=repositories*
  12. // @grant none
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_addStyle
  16. // @grant GM_registerMenuCommand
  17. // @grant none
  18. // @icon https://github.githubassets.com/pinned-octocat.svg
  19. // @license MIT
  20. // @source https://github.com/qinwuyuan-cn/UserScripts
  21. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  22. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  23. // ==/UserScript==
  24. "use strict"
  25. //! Generate a new public access token from https://github.com/settings/tokens and insert it here
  26. //*Note: to be able to see the size of your private repos, you need to select the `repo` scope when generating the token
  27. let TOKEN = GM_getValue('githubToken', "")
  28. GM_registerMenuCommand('Set GitHub Token', function () {
  29. showPrompt()
  30. })
  31. function showPrompt() {
  32. let promptDiv = document.createElement('div')
  33. promptDiv.id = 'github-token-prompt'
  34. promptDiv.innerHTML = `
  35. <div style="position:fixed;top:10%;left:50%;transform:translateX(-50%);width:300px;background:white;border:1px solid #ccc;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,0.1);">
  36. <h3>Set GitHub Token</h3>
  37. <input type="text" id="github-token-input" style="width:100%;" value="${TOKEN}">
  38. <button id="save-token" style="background-color: #28a745; color: white; border: none; padding: 10px 20px; cursor: pointer; border-radius: 4px; margin-top: 10px;">Save</button>
  39. <button id="cancel-token" style="background-color: #dc3545; color: white; border: none; padding: 10px 20px; cursor: pointer; border-radius: 4px; margin-top: 10px;">Cancel</button>
  40. <a href="https://github.com/settings/tokens/new?description=repo-size%20userscript&scopes=repo" target="_blank" style="position: fixed; bottom: 10px; right: 10px; z-index: 10000; text-decoration: underline; color: blue;">New personal access token</a>
  41. </div>
  42. `
  43. document.body.appendChild(promptDiv)
  44. document.getElementById('save-token').addEventListener('click', function () {
  45. let tokenValue = document.getElementById('github-token-input').value
  46. GM_setValue('githubToken', tokenValue)
  47. TOKEN = GM_getValue('githubToken', "")
  48.  
  49. document.getElementById('github-token-prompt').remove()
  50. })
  51. document.getElementById('cancel-token').addEventListener('click', function () {
  52. document.getElementById('github-token-prompt').remove()
  53. })
  54. GM_addStyle(`
  55. #github-token-prompt {
  56. z-index: 10000;
  57. }
  58. `)
  59. }
  60. const getPageType = () => {
  61. const { pathname, search } = window.location
  62. const params = new URLSearchParams(search)
  63. const [, username, repo] = pathname.split("/")
  64. const q = params.get("q")?.toLocaleLowerCase()
  65. const type = params.get("type")?.toLocaleLowerCase()
  66. if (window.location.pathname.split('/').pop() === "repositories") return "list-view-container"
  67. if (window.location.href.includes("?tab=repositories")) return "user-repositories"
  68. if (username && repo) return "repo"
  69. if (q && type === "code") return "code_search"
  70. if (q) return "search"
  71. }
  72. const addSizeToRepos = () => {
  73. const pageType = getPageType()
  74. // Get the repo selector based on the page type
  75. let repoSelector
  76. switch (pageType) {
  77. case "repo"://仓库详情界面
  78. repoSelector = "#repository-container-header strong a"
  79. break
  80. case "list-view-container"://ORG下的仓库列表
  81. repoSelector = 'div[data-testid="list-view-item-title-container"] h4 a'
  82. break
  83. case "user-repositories"://用户资料页面的仓库TAB
  84. repoSelector = '#user-repositories-list h3 a'
  85. break
  86. case "search"://搜索
  87. repoSelector = 'div[data-testid="results-list"] .search-title a'
  88. break
  89. case "code_search"://代码搜索
  90. repoSelector = 'div[data-testid="results-list"] .search-title a'
  91. break
  92. default:
  93. return
  94. }
  95. function extractPath(input) {
  96. const thirdSlashIndex = input.indexOf('/', input.indexOf('/', input.indexOf('/') + 1) + 1)
  97. if (thirdSlashIndex !== -1) {
  98. return input.substring(0, thirdSlashIndex)
  99. }
  100. return input
  101. }
  102. // Get all the repo links
  103. document.querySelectorAll(repoSelector).forEach(async (elem) => {
  104. // Get json data from github api to extract the size
  105. const tkn = TOKEN
  106. var href = elem.getAttribute("href")
  107. href = extractPath(href)
  108. console.log(href)
  109. const headers = tkn ? { authorization: `token ${tkn}` } : {}
  110. const jsn = await (
  111. await fetch(`https://api.github.com/repos${href}`, {
  112. headers: headers,
  113. })
  114. ).json()
  115. // If JSON failed to load, skip
  116. if (jsn.message) return
  117. // Get parent element to append the size to
  118. let parent = elem.parentElement
  119. if (pageType === "repo") {
  120. parent = elem.parentElement.parentElement
  121. }
  122. // Create the size container
  123. let sizeContainer = parent.querySelector(`#mshll-repo-size`)
  124. if (sizeContainer === null) {
  125. sizeContainer = document.createElement("span")
  126. sizeContainer.id = "mshll-repo-size"
  127. sizeContainer.classList.add("Label", "Label--info", "v-align-middle", "ml-1")
  128. sizeContainer.setAttribute("title", "Repository size")
  129. sizeContainer.innerText = "-"
  130. // Create the size icon
  131. let sizeSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg")
  132. sizeSVG.setAttribute("aria-hidden", "true")
  133. sizeSVG.setAttribute("viewBox", "-4 -4 22 22")
  134. sizeSVG.setAttribute("width", "16")
  135. sizeSVG.setAttribute("height", "16")
  136. sizeSVG.setAttribute("fill", "currentColor")
  137. sizeSVG.setAttribute("data-view-component", "true")
  138. sizeSVG.classList.add("octicon", "octicon-file-directory", "mr-1")
  139. let sizeSVGPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
  140. sizeSVGPath.setAttribute("fill-rule", "evenodd")
  141. sizeSVGPath.setAttribute("d", "M1 3.5c0-.626.292-1.165.7-1.59.406-.422.956-.767 1.579-1.041C4.525.32 6.195 0 8 0c1.805 0 3.475.32 4.722.869.622.274 1.172.62 1.578 1.04.408.426.7.965.7 1.591v9c0 .626-.292 1.165-.7 1.59-.406.422-.956.767-1.579 1.041C11.476 15.68 9.806 16 8 16c-1.805 0-3.475-.32-4.721-.869-.623-.274-1.173-.62-1.579-1.04-.408-.426-.7-.965-.7-1.591Zm1.5 0c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 5.205 6.353 5.5 8 5.5c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55 0-.133-.058-.318-.282-.551-.227-.237-.591-.483-1.101-.707C11.102 1.795 9.647 1.5 8 1.5c-1.646 0-3.101.295-4.118.742-.508.224-.873.471-1.1.708-.224.232-.282.417-.282.55Zm0 4.5c0 .133.058.318.282.551.227.237.591.483 1.101.707C4.898 9.705 6.353 10 8 10c1.646 0 3.101-.295 4.118-.742.508-.224.873-.471 1.1-.708.224-.232.282-.417.282-.55V5.724c-.241.15-.503.286-.778.407C11.475 6.68 9.805 7 8 7c-1.805 0-3.475-.32-4.721-.869a6.15 6.15 0 0 1-.779-.407Zm0 2.225V12.5c0 .133.058.318.282.55.227.237.592.484 1.1.708 1.016.447 2.471.742 4.118.742 1.647 0 3.102-.295 4.117-.742.51-.224.874-.47 1.101-.707.224-.233.282-.418.282-.551v-2.275c-.241.15-.503.285-.778.406-1.247.549-2.917.869-4.722.869-1.805 0-3.475-.32-4.721-.869a6.327 6.327 0 0 1-.779-.406Z")
  142. sizeSVG.appendChild(sizeSVGPath)
  143. // Convert the size to human readable
  144. const sizes = ["B", "KB", "MB", "GB", "TB"]
  145. const size = jsn.size * 1024 // Github API returns size in KB so convert to bytes
  146. let i = parseInt(Math.floor(Math.log(size) / Math.log(1024)))
  147. const humanReadableSize = (size / Math.pow(1024, i)).toFixed(1) + " " + sizes[i]
  148. // Insert the size into the size container
  149. sizeContainer.innerHTML = `${humanReadableSize}`
  150. sizeContainer.prepend(sizeSVG)
  151. // Insert the size container into the DOM
  152. if (pageType === "code_search") {
  153. parent.style.direction = 'ltr'
  154. }
  155. parent.appendChild(sizeContainer)
  156. }
  157. })
  158. }
  159. window.addSizeToRepos = addSizeToRepos
  160. // Add the size to the repos on the page
  161. //addSizeToRepos()
  162. window.onload = function () {
  163. addSizeToRepos()
  164. }
  165. // Watch for URL changes
  166. let lastUrl = location.href
  167. new MutationObserver(() => {
  168. const url = location.href
  169. if (url !== lastUrl) {
  170. lastUrl = url
  171. setTimeout(function () {
  172. //NOTE - 此处增加延时了,就这样得了
  173. addSizeToRepos()
  174. }, 1500)
  175. }
  176. }).observe(document, { subtree: true, childList: true })