├── manifest.json ├── README.md ├── content.js ├── popup.html ├── popup.js └── background.js /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StarHub", 3 | "version": "1.0", 4 | "manifest_version": 2, 5 | "browser_action": { 6 | "default_title": "StarHub", 7 | "default_popup": "popup.html" 8 | }, 9 | "content_scripts": [ 10 | { 11 | "matches": [ 12 | "https://github.com/*" 13 | ], 14 | "js": [ 15 | "content.js" 16 | ] 17 | } 18 | ], 19 | "background": { 20 | "scripts": ["background.js"] 21 | }, 22 | "permissions": [ 23 | "http://*/*", 24 | "https://*/*", 25 | "tabs", 26 | "storage" 27 | ] 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StarHub 2 | 3 | StarHub is a chrome extension that tracks your GitHub stars so you don't have to. 4 | 5 |
6 | 7 | ## Features 8 | 9 | - Quickly sign-in through your GitHub account to get started. 10 | - The extension fetches all the repositories you have access over. 11 | - Track stars and unstars on your repository by simply adding it your tracking list.

12 |

13 | - Know when and who unstarred your repository by clicking on the unstars icon on your repository's page or by going to ```https://github.com/(repo-owner)/(repo-name)/stargazers```.

14 | 15 | 16 |

17 |

18 | 19 | ## Getting Started 20 | 21 | 1. Install the extension from [here](https://github.com/techsyndicate/starhub). 22 | 2. Once you have it, you'll need to login through GitHub to start tracking. 23 | 3. On completing the authentication, you can add a repository for tracking by clicking on the "Add Repository" button on the home screen.

24 | 25 | 26 | 4. And that's it. You can now track stars on any of your GitHub repositories! 27 | 28 | ## Contributing 29 | 30 | We, as developers, want to give you the best possible user experience, and thus we encourage you to open issues on our GitHub repository for any bugs, crashes, or anomalous behavior which you face. 31 | 32 | Check out the backend API [here](https://github.com/techsyndicate/starhub-api). 33 | 34 | -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | const current_url = location.href 2 | const splitted_url = current_url.split('https://github.com/') 3 | const repo_name = splitted_url[1] 4 | 5 | function parseISOString(s) { 6 | var b = s.split(/\D+/); 7 | return new Date(Date.UTC(b[0], --b[1], b[2], b[3], b[4], b[5], b[6])); 8 | } 9 | 10 | if(repo_name.split('/').length < 3) { 11 | chrome.runtime.sendMessage({text: "getUpdatedData"}, function(response) { 12 | if(response.message == "repos found") { 13 | const repos = response.repos 14 | let show_icon = false 15 | let unstar_count = 0 16 | for(let i = 0; i < repos.length; i++) { 17 | if(repos[i].repoName == repo_name) { 18 | show_icon = true 19 | unstar_count = repos[i].unstars.length 20 | } 21 | } 22 | if(show_icon == true) { 23 | const stargazers_url = "https://github.com/" + repo_name + "/stargazers" 24 | let iconBar = document.getElementsByClassName("pagehead-actions")[0] 25 | iconBar.innerHTML = iconBar.innerHTML + `
  • 26 |
    27 | 30 | 31 |
    32 |
  • ` 33 | } 34 | } 35 | }) 36 | } else { 37 | chrome.runtime.sendMessage({text: "getUpdatedData"}, function(response) { 38 | if(response.message == "repos found") { 39 | const repos = response.repos 40 | let show_icon = false 41 | let unstar_count = 0 42 | let unstars = [] 43 | for(let i = 0; i < repos.length; i++) { 44 | if(`${repos[i].repoName}/stargazers` == repo_name) { 45 | show_icon = true 46 | unstar_count = repos[i].unstars.length 47 | unstars = repos[i].unstars 48 | } 49 | } 50 | if(show_icon == true) { 51 | const repos_div = document.getElementById("repo-content-pjax-container") 52 | console.log(repos_div); 53 | let intial_html = `
    54 |

    Unstargazers

    55 |
    56 |
    61 | 62 |
      ` 63 | for(let i = 0; i < unstars.length; i++) { 64 | const d = new Date(unstars[i].unstarredAt); 65 | const ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d); 66 | const mo = new Intl.DateTimeFormat('en', { month: 'short' }).format(d); 67 | const da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d); 68 | intial_html += `` 81 | } 82 | const ending_html = `
    83 |
    84 |
    ` 85 | repos_div.innerHTML = intial_html + ending_html +repos_div.innerHTML 86 | } 87 | } 88 | }) 89 | } 90 | 91 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | StarHub 5 | 6 | 7 | 124 | 125 | 126 | 127 | 128 | 165 | 166 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("click", function (e) { 2 | if (e.target.id == "authButton") { 3 | chrome.runtime.sendMessage({text: "auth"}); 4 | } 5 | if(e.target.id == "logoutButton") { 6 | chrome.runtime.sendMessage({text: "logout"}) 7 | } 8 | if(e.target.id == "aboutButton") { 9 | window.open("https://github.com/techsyndicate/starhub") 10 | } 11 | if(e.target.id == "deleteButton") { 12 | chrome.runtime.sendMessage({text: "delete"}, function(response) { 13 | if(response.message == "deleted") { 14 | chrome.runtime.sendMessage({text: "updateCache"}, function(response) { 15 | location.reload(); 16 | }) 17 | } 18 | }) 19 | } 20 | if(e.target.id == "refreshButton") { 21 | chrome.runtime.sendMessage({text: "updateCache"}, function(response) { 22 | location.reload(); 23 | }) 24 | } 25 | if(e.target.id == "addRepoButton") { 26 | document.getElementById("repos").style.display = "none"; 27 | document.getElementById("message").style.display = "block"; 28 | chrome.runtime.sendMessage({text: "getRepos"}, function(response) { 29 | if(response.message == "error") { 30 | document.getElementById("message").innerHTML = "an error occurred"; 31 | } else { 32 | const options = response.message 33 | const repoSelector = document.getElementById("repoSelector") 34 | for(let i=0; i < options.length;i++) { 35 | const option = '' 36 | repoSelector.innerHTML = repoSelector.innerHTML + option 37 | } 38 | document.getElementById("message").style.display = "none"; 39 | document.getElementById("addRepo").style.display = "block"; 40 | } 41 | }) 42 | } 43 | if(e.target.id == "addRepoAfterSelectButton") { 44 | const e = document.getElementById("repoSelector"); 45 | const repoName = e.options[e.selectedIndex].value; 46 | chrome.runtime.sendMessage({text: "addRepoWebhook", repoName: repoName}, function(response) { 47 | document.getElementById("addRepo").style.display = "none"; 48 | document.getElementById("message").style.display = "block"; 49 | if(response.type == "negative") { 50 | document.getElementById("message").innerHTML = response.message; 51 | } else { 52 | chrome.runtime.sendMessage({text: "updateCache"}, function(response) { 53 | location.reload(); 54 | }) 55 | } 56 | }) 57 | } 58 | if(e.target.id == "addOrgRepoButton") { 59 | chrome.runtime.sendMessage({text: "addOrgRepoButton"}, function(response) { 60 | document.getElementById("addRepo").style.display = "none"; 61 | document.getElementById("message").style.display = "block"; 62 | if(response.type == "negative") { 63 | document.getElementById("message").innerHTML = response.message; 64 | } else { 65 | chrome.runtime.sendMessage({text: "updateCache"}, function(response) { 66 | location.reload(); 67 | }) 68 | } 69 | }) 70 | } 71 | }); 72 | 73 | document.addEventListener('DOMContentLoaded', function () { 74 | chrome.runtime.sendMessage({text: "getData"}, function(response) { 75 | if(response.message == false) { 76 | document.getElementById("authButton").style.display = "block"; 77 | } else { 78 | if(response.repos == "no repos found") { 79 | document.getElementById("repos-list-error").style.display = "block"; 80 | document.getElementById("repos-table").style.display = "none"; 81 | } else { 82 | const repos = response.repos 83 | for(let i = 0; i < repos.length; i++) { 84 | document.getElementById("repos-table").innerHTML = document.getElementById("repos-table").innerHTML + `${repos[i].repoName}` 85 | } 86 | } 87 | document.getElementById("username").innerHTML = response.username; 88 | document.getElementById("pfp").src = "https://github.com/"+response.username+".png"; 89 | document.getElementById("username").style.display = "block"; 90 | document.getElementById("pfp").style.display = "block"; 91 | document.getElementById("repos").style.display = "block"; 92 | document.getElementById("logoutButton").style.display = "block"; 93 | document.getElementById("aboutButton").style.display = "block"; 94 | } 95 | }) 96 | }); -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){ 2 | if (message.text == "auth") { 3 | chrome.storage.local.get(['accesstoken'], function(result) { 4 | if(result.accesstoken != null) { 5 | alert('already logged in') 6 | } else { 7 | chrome.storage.local.set({authState: true}, function() { 8 | console.log('auth state set true'); 9 | window.open('https://starhub.ml/auth'); 10 | }); 11 | } 12 | }) 13 | } 14 | if (message.text == "logout") { 15 | const logoutBoolean = confirm("Are you sure you want to logout?") 16 | if(logoutBoolean) { 17 | chrome.storage.local.set({accesstoken: null, username: null}, function() { 18 | chrome.runtime.reload() 19 | }); 20 | } 21 | } 22 | }); 23 | 24 | chrome.runtime.onMessage.addListener( 25 | function(request, sender, sendResponse) { 26 | if (request.text == "getData") { 27 | chrome.storage.local.get(['accesstoken'], function(result) { 28 | const token = result.accesstoken 29 | if(token != null) { 30 | chrome.storage.local.get(['username'], function(result) { 31 | const username = result.username 32 | chrome.storage.local.get(['repos'], function(result) { 33 | if(result.repos != null) { 34 | sendResponse({message: true, username: username, repos: result.repos}); 35 | } else { 36 | const requestOptions = { 37 | method: 'GET', 38 | redirect: 'follow' 39 | }; 40 | 41 | fetch(`https://starhub.ml/repos?token=${token}`, requestOptions) 42 | .then(async function(results) { 43 | const parsed = await results.json() 44 | if(parsed.message == "no repos found") { 45 | chrome.storage.local.set({repos: "no repos found"}, function() { 46 | sendResponse({message: true, username: username, repos: "no repos found"}); 47 | }); 48 | } else { 49 | chrome.storage.local.set({repos: parsed}, function() { 50 | sendResponse({message: true, username: username, repos: parsed}); 51 | }); 52 | } 53 | }) 54 | .catch(error => sendResponse({message: "error"})); 55 | } 56 | }) 57 | }) 58 | } else { 59 | sendResponse({message: false}) 60 | } 61 | }) 62 | } 63 | return true; 64 | } 65 | ); 66 | 67 | chrome.runtime.onMessage.addListener( 68 | function(request, sender, sendResponse) { 69 | if (request.text == "getRepos") { 70 | chrome.storage.local.get(['accesstoken'], function(result) { 71 | if(result.accesstoken != null) { 72 | chrome.storage.local.get(['username'], function(result) { 73 | const requestOptions = { 74 | method: 'GET', 75 | redirect: 'follow' 76 | }; 77 | 78 | fetch(`https://api.github.com/users/${result.username}/repos`, requestOptions) 79 | .then(async function(results) { 80 | const parsed = await results.json() 81 | let options = [] 82 | for(let i = 0; i < parsed.length; i++) { 83 | options.push(parsed[i].name); 84 | } 85 | sendResponse({message: options}) 86 | }) 87 | .catch(error => sendResponse({message: "error"})); 88 | }) 89 | } else { 90 | sendResponse({message: "error"}) 91 | } 92 | }) 93 | } 94 | return true; 95 | } 96 | ); 97 | 98 | chrome.runtime.onMessage.addListener( 99 | function(request, sender, sendResponse) { 100 | if (request.text == "addRepoWebhook") { 101 | const repoName = request.repoName 102 | chrome.storage.local.get(['accesstoken'], function(result) { 103 | if(result.accesstoken != null) { 104 | const accesstoken = result.accesstoken 105 | chrome.storage.local.get(['username'], function(result) { 106 | const username = result.username 107 | const repoOwner = username 108 | const requestOptions = { 109 | method: 'GET', 110 | redirect: 'follow' 111 | }; 112 | 113 | fetch(`https://starhub.ml/webhook?repository=${repoName}&user=${username}&token=${accesstoken}&repoOwner=${repoOwner}`, requestOptions) 114 | .then(async function(result) { 115 | const parsed = await result.json() 116 | if(parsed.message == "repo already exists") { 117 | sendResponse({message: "repo is already being tracked", type: "negative"}) 118 | } else { 119 | if(parsed.message == "error") { 120 | sendResponse({message: "an error occurred", type: "negative"}) 121 | } else { 122 | sendResponse({message: "repo added", type: "positive"}) 123 | } 124 | } 125 | }) 126 | .catch(error => sendResponse({message: "error", type: "negative"})); 127 | }) 128 | } else { 129 | sendResponse({message: "error", type: "negative"}) 130 | } 131 | }) 132 | } 133 | return true; 134 | } 135 | ); 136 | 137 | chrome.runtime.onMessage.addListener( 138 | function(request, sender, sendResponse) { 139 | if (request.text == "addOrgRepoButton") { 140 | const orgRepoName = prompt("Enter organization repository name in the following format: (/)", "/") 141 | if(orgRepoName != null) { 142 | if(orgRepoName.split("/").length > 2) { 143 | alert("invalid format") 144 | } else { 145 | const repoName = orgRepoName.split("/")[1] 146 | const repoOwner = orgRepoName.split("/")[0] 147 | chrome.storage.local.get(['accesstoken'], function(result) { 148 | if(result.accesstoken != null) { 149 | const accesstoken = result.accesstoken 150 | chrome.storage.local.get(['username'], function(result) { 151 | const username = result.username 152 | const requestOptions = { 153 | method: 'GET', 154 | redirect: 'follow' 155 | }; 156 | 157 | fetch(`https://starhub.ml/webhook?repository=${repoName}&user=${username}&token=${accesstoken}&repoOwner=${repoOwner}`, requestOptions) 158 | .then(async function(result) { 159 | const parsed = await result.json() 160 | if(parsed.message == "repo already exists") { 161 | sendResponse({message: "repo is already being tracked", type: "negative"}) 162 | } else { 163 | if(parsed.message == "error") { 164 | sendResponse({message: "an error occurred", type: "negative"}) 165 | } else { 166 | sendResponse({message: "repo added", type: "positive"}) 167 | } 168 | } 169 | }) 170 | .catch(error => sendResponse({message: "error", type: "negative"})); 171 | }) 172 | } else { 173 | sendResponse({message: "error", type: "negative"}) 174 | } 175 | }) 176 | } 177 | } 178 | } 179 | } 180 | ); 181 | 182 | chrome.runtime.onMessage.addListener( 183 | function(request, sender, sendResponse) { 184 | if (request.text == "updateCache") { 185 | chrome.storage.local.get(['accesstoken'], function(result) { 186 | const token = result.accesstoken 187 | if(token != null) { 188 | const requestOptions = { 189 | method: 'GET', 190 | redirect: 'follow' 191 | }; 192 | 193 | fetch(`https://starhub.ml/repos?token=${token}`, requestOptions) 194 | .then(async function(results) { 195 | const parsed = await results.json() 196 | if(parsed.message == "no repos found") { 197 | chrome.storage.local.set({repos: "no repos found"}, function() { 198 | sendResponse({message: "updated"}); 199 | }); 200 | } else { 201 | chrome.storage.local.set({repos: parsed}, function() { 202 | sendResponse({message: "updated"}); 203 | }); 204 | } 205 | }) 206 | .catch(error => sendResponse({message: "error"})); 207 | } else { 208 | sendResponse({message: "error"}) 209 | } 210 | }) 211 | } 212 | } 213 | ) 214 | 215 | chrome.runtime.onMessage.addListener( 216 | function(request, sender, sendResponse) { 217 | if (request.text == "getUpdatedData") { 218 | chrome.storage.local.get(['accesstoken'], function(result) { 219 | const token = result.accesstoken 220 | if(token != null) { 221 | const requestOptions = { 222 | method: 'GET', 223 | redirect: 'follow' 224 | }; 225 | 226 | fetch(`https://starhub.ml/repos?token=${token}`, requestOptions) 227 | .then(async function(results) { 228 | const parsed = await results.json() 229 | if(parsed.message == "no repos found") { 230 | sendResponse({message: "no repos found"}) 231 | } else { 232 | sendResponse({message: "repos found", repos: parsed}) 233 | } 234 | }) 235 | .catch(error => sendResponse({message: "error"})); 236 | } else { 237 | sendResponse({message: "error"}) 238 | } 239 | }) 240 | } 241 | } 242 | ) 243 | 244 | chrome.runtime.onMessage.addListener( 245 | function(request, sender, sendResponse) { 246 | if (request.text == "delete") { 247 | const repoName = prompt("Enter repository name which you want to delete in the following format: (/)", "/") 248 | if(repoName != null) { 249 | if(repoName.split("/").length > 2) { 250 | alert("Invalid format") 251 | } else { 252 | const repoOwner = repoName.split("/")[0] 253 | const repository = repoName.split("/")[1] 254 | chrome.storage.local.get(['accesstoken'], function(result) { 255 | const token = result.accesstoken 256 | if(token != null) { 257 | const requestOptions = { 258 | method: 'GET', 259 | redirect: 'follow' 260 | }; 261 | 262 | fetch(`https://starhub.ml/repos/delete?token=${token}&repository=${repository}&repoOwner=${repoOwner}`, requestOptions) 263 | .then(async function(response) { 264 | const parsed = await response.json() 265 | if(parsed.message == "deleted successfully") { 266 | sendResponse({message: "deleted"}) 267 | } else { 268 | sendResponse({message: "error"}) 269 | } 270 | }) 271 | .catch(error => sendResponse({message: "error"})); 272 | } else { 273 | sendResponse({message: "error"}) 274 | } 275 | }) 276 | } 277 | } 278 | } 279 | } 280 | ) 281 | 282 | async function auth() { 283 | chrome.tabs.query({'active': true, 'windowId': chrome.windows.WINDOW_ID_CURRENT}, 284 | function(tabs){ 285 | if(tabs[0].url.startsWith("http://localhost:15015/callback")) { 286 | chrome.storage.local.get(['authState'], function(result) { 287 | chrome.storage.local.set({authState: false}, function() { 288 | console.log('auth state set false') 289 | }) 290 | if (result.authState == true) { 291 | const split = tabs[0].url.split('/') 292 | const accesstoken = split[4] 293 | const username = split[5] 294 | const userId = split[6] 295 | chrome.storage.local.set({accesstoken: accesstoken, username: username, userId: userId}, function() { 296 | console.log('user state set'); 297 | }); 298 | chrome.tabs.update({url: "http://starhub.ml/auth/success"}); 299 | } 300 | }); 301 | } 302 | } 303 | ); 304 | } 305 | 306 | chrome.tabs.onUpdated.addListener(auth) 307 | --------------------------------------------------------------------------------