GitHub: PR author avatar as tab icon

Sets GitHub PR tab icon (favicon) to author's avatar

安装此脚本?
作者推荐脚本

你可能也喜欢 GitHub: copy commit reference

安装此脚本
  1. // ==UserScript==
  2. // @name GitHub: PR author avatar as tab icon
  3. // @namespace https://github.com/rybak
  4. // @version 6
  5. // @description Sets GitHub PR tab icon (favicon) to author's avatar
  6. // @author Andrei Rybak
  7. // @homepageURL https://github.com/rybak/github-pr-avatars-tab-icons
  8. // @license MIT
  9. // @match https://github.com/*/pull/*
  10. // @icon https://github.githubassets.com/favicons/favicon-dark.png
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. /*
  15. * Copyright (c) 2023 Andrei Rybak
  16. *
  17. * Permission is hereby granted, free of charge, to any person obtaining a copy
  18. * of this software and associated documentation files (the "Software"), to deal
  19. * in the Software without restriction, including without limitation the rights
  20. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  21. * copies of the Software, and to permit persons to whom the Software is
  22. * furnished to do so, subject to the following conditions:
  23. *
  24. * The above copyright notice and this permission notice shall be included in all
  25. * copies or substantial portions of the Software.
  26. *
  27. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  30. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  33. * SOFTWARE.
  34. */
  35.  
  36. /*
  37. * Doesn't work very good, because GitHub's tab icons aren't static.
  38. * Results of GitHub Actions builds are reflected in the icons dynamically.
  39. */
  40.  
  41. (function() {
  42. 'use strict';
  43.  
  44. const LOG_PREFIX = '[PR avatars]';
  45.  
  46. function error(...toLog) {
  47. console.error(LOG_PREFIX, ...toLog);
  48. }
  49.  
  50. function log(...toLog) {
  51. console.log(LOG_PREFIX, ...toLog);
  52. }
  53.  
  54. /*
  55. * Extracts the parts 'owner_repo' and 'pull_number' out of the current page's URL.
  56. */
  57. function getPullRequestUrlParts() {
  58. /*
  59. * URL might be just a PR link:
  60. * https://github.com/rybak/atlassian-tweaks/pull/10
  61. * or a subpage of a PR:
  62. * https://github.com/rybak/atlassian-tweaks/pull/10/commits
  63. * Result will be
  64. * 'rybak/atlassian-tweaks' and '10'
  65. */
  66. const m = document.location.pathname.match("^/(.*)/pull/(\\d+)");
  67. if (!m) {
  68. error("Cannot extract owner_repo and pull_number for REST API URL from", document.location.pathname);
  69. return null;
  70. }
  71. return {
  72. 'owner_repo': m[1],
  73. 'pull_number': m[2]
  74. };
  75. }
  76.  
  77. /*
  78. * Generates a URL for accessing the data about the pull request via GitHub's REST API.
  79. * https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request
  80. */
  81. function getRestApiPullRequestUrl() {
  82. const parts = getPullRequestUrlParts();
  83. if (!parts) {
  84. return null;
  85. }
  86. return `https://api.github.com/repos/${parts.owner_repo}/pulls/${parts.pull_number}`;
  87. }
  88.  
  89. /*
  90. * Replaces favicon with the PR author's avatar.
  91. */
  92. async function setFavicon() {
  93. const prUrl = getRestApiPullRequestUrl();
  94. if (!prUrl) {
  95. return;
  96. }
  97. try {
  98. const response = await fetch(prUrl);
  99. const json = await response.json();
  100. const avatarUrl = json.user.avatar_url;
  101. if (!avatarUrl) {
  102. error("Cannot find the avatar URL", json);
  103. return;
  104. }
  105. const faviconNodes = document.querySelectorAll('link[rel="icon"], link[rel="alternate icon"]');
  106. if (!faviconNodes || faviconNodes.length == 0) {
  107. error("Cannot find favicon elements.");
  108. return;
  109. }
  110. log("New URL", avatarUrl);
  111. faviconNodes.forEach(node => {
  112. log("Replacing old URL =", node.href);
  113. node.href = avatarUrl;
  114. });
  115. } catch (e) {
  116. error(`Cannot load ${prUrl}. Got error`, e);
  117. }
  118. }
  119.  
  120. setFavicon();
  121. })();