├── 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 |
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 |
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 += `
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
Unstarred on: ${da} ${mo}, ${ye}
78 |
79 |
80 | `
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 |
129 |
StarHub
130 |
Sign in with github
131 |
132 |
133 |
loading...
134 |
135 |
136 |
140 |
141 |
No repositories are being tracked
142 |
144 |
145 |
146 |
Add respository
147 |
148 |
149 |
150 |
151 |
154 |
155 |
156 | Add selected repository
157 | Add an organization repository
158 |
159 |
160 |
164 |
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 = '' + options[i] + ' '
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 |
--------------------------------------------------------------------------------