Home Create a Github Repo Star Notifier for Free with Google Apps Script in Three Simple Steps
Post
Cancel

Create a Github Repo Star Notifier for Free with Google Apps Script in Three Simple Steps

How to Build a Github Repo Star Notifier for Free in Three Simple Steps Using Google Apps Script

Writing a GAS script to integrate with Github Webhook and forward star (like) notifications to Line.

Introduction

As maintainers of open-source projects, we are not driven by money or fame, but by a sense of vanity. Every time we see a new ⭐️ star, it brings immense joy to our hearts. Knowing that the projects we invest our time and energy into are being used and actually helping friends with similar problems is truly rewarding.

Star History Chart

Star History Chart

Because of this, I tend to have a slight obsession with ⭐️ stars. I find myself frequently checking Github to see if the ⭐️ star count has increased. I wondered if there could be a more proactive way to receive notifications when someone hits that ⭐️ star, without having to manually track and search for it.

Existing Tools

Firstly, let’s consider looking for existing tools to achieve this. I searched on Github Marketplace and found several awesome tools that are available.

I tried some of them, but the results were not as expected. Some of them are no longer operational, while others can only send notifications when reaching every 5/10/20 ⭐️ stars (I’m just a small user, so getting 1 ⭐️ already makes me happy 😝). Additionally, these tools only offer email notifications, but I prefer using SNS notifications.

Moreover, I feel a bit uneasy about installing an app just for the sake of “vanity,” as I’m concerned about potential cybersecurity risks.

On iOS, both the Github app and GitTrends and other third-party apps do not support this feature.

Building Your Own Github Repo Star Notifier

Based on the above, we can actually use Google Apps Script for free and quickly create our own Github Repo Star Notifier.

Preparation

In this article, we’ll use Line as the notification medium. If you want to use other communication/SNS software for notification, you can ask ChatGPT how to implement it.

Ask [ChatGPT](https://chat.openai.com){:target="_blank"} how to implement Line Notify

Ask ChatGPT how to implement Line Notify

lineToken:

  • Go to Line Notify

  • After logging in to your Line account, scroll down to find the “Generate access token (For developers)” section.

Generate access token (For developers) in Line Notify

  • Click on “Generate token”

Generate token

  • Token Name: Enter the desired bot title, which will be displayed before the message (e.g., Github Repo Notifer: XXXX).

  • Choose where the message should be sent: I choose “1-on-1 chat with LINE Notify” to send messages to myself via LINE Notify’s official bot.

  • Click on “Generate token”

Generate token

  • Select “Copy”

  • Make sure to note down the Token, as it won’t be visible if you forget it and you’ll need to generate a new one.

githubWebhookSecret:

Generate a random string

  • Copy & remember this random string.

We will use this string as the verification medium between Github Webhook and Google Apps Script.

Due to GAS limitations, it’s not possible to access the Headers content in doPost(e). Therefore, the standard authentication method for Github Webhooks cannot be used in Google Apps Script. We can only manually perform string matching authentication using ?secret=.

Creating Google Apps Script

Go to Google Apps Script, and click on the top left corner, “+ New Project”.

**Google Apps Script**

Google Apps Script

Click on the top-left corner, “Untitled Project,” to rename the project.

Rename the project

Here, I’ll name the project “My-Github-Repo-Notifier” for easy identification in the future.

Code Input Area:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Constant variables
const lineToken = 'XXXX';
// Generate yours line notify bot token: https://notify-bot.line.me/my/
const githubWebhookSecret = "XXXXX";
// Generate yours secret string here: https://www.random.org/strings/?num=1&len=32&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new

// HTTP Get/Post Handler
// Do not allow Get method
function doGet(e) {
	return HtmlService.createHtmlOutput("Access Denied!");

}

// Github Webhook will use Post method to enter
function doPost(e) {

	const content = JSON.parse(e.postData.contents);

	// Security check to ensure the request is from Github Webhook
	if (verifyGitHubWebhook(e) == false) {
		return HtmlService.createHtmlOutput("Access Denied!");

	}

	// Star payload data content["action"] == "started"
	if (content["action"] != "started") {
		return HtmlService.createHtmlOutput("OK!");

	}

	// Compose the message
	const message = makeMessageString(content);

	// Send the message, can also be sent to Slack, Telegram...
	sendLineNotifyMessage(message);

	return HtmlService.createHtmlOutput("OK!");

}

// Method
// Generate message content
function makeMessageString(content) {

	const repository = content["repository"];
	const repositoryName = repository["name"];
	const repositoryURL = repository["svn_url"];
	const starsCount = repository["stargazers_count"];
	const forksCount = repository["forks_count"];
	const starrer = content["sender"]["login"];

	var message = "🎉🎉 \"" + starrer + "\" starred your \"" + repositoryName + "\" Repo 🎉🎉\n";
	message += "Current total stars: " + starsCount + "\n";
	message += "Current total forks: " + forksCount + "\n";
	message += repositoryURL;

	return message;
}

// Verify if the request is from Github Webhook
// Due to GAS limitations (https://issuetracker.google.com/issues/67764685?pli=1)
// It's not possible to get Headers content in doPost(e)
// So the standard authentication method for Github Webhooks (https://docs.github.com/en/webhooks-and-events/webhooks/securing-your-webhooks)
// cannot be used, only manual string matching authentication with ?secret=XXX
function verifyGitHubWebhook(e) {
	if (e.parameter["secret"] === githubWebhookSecret) {
		return true
	} else {
		return false
	}
}

// -- Send Message --
// Line
// For other message delivery methods, you can ask ChatGPT
function sendLineNotifyMessage(message) {
	var url = 'https://notify-api.line.me/api/notify';
	var options = {
		method: 'post',
		headers: {
			'Authorization': 'Bearer ' + lineToken
		},
		payload: {
			'message': message
		}

	};
	UrlFetchApp.fetch(url, options);
}

Replace lineToken & githubWebhookSecret with the values you copied in the previous steps.

Supplemental data received when someone presses Star on Github Webook is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
{
  "action": "created",
  "starred_at": "2023-08-01T03:42:26Z",
  "repository": {
    "id": 602927147,
    "node_id": "R_kgDOI-_wKw",
    "name": "ZMarkupParser",
    "full_name": "ZhgChgLi/ZMarkupParser",
    "private": false,
    "owner": {
      "login": "ZhgChgLi",
      "id": 83232222,
      "node_id": "MDEyOk9yZ2FuaXphdGlvbjgzMjMyMjIy",
      "avatar_url": "https://avatars.githubusercontent.com/u/83232222?v=4",
      "gravatar_id": "",
      "url": "https://api.github.com/users/ZhgChgLi",
      "html_url": "https://github.com/ZhgChgLi",
      "followers_url": "https://api.github.com/users/ZhgChgLi/followers",
      "following_url": "https://api.github.com/users/ZhgChgLi/following{/other_user}",
      "gists_url": "https://api.github.com/users/ZhgChgLi/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/ZhgChgLi/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/ZhgChgLi/subscriptions",
      "organizations_url": "https://api.github.com/users/ZhgChgLi/orgs",
      "repos_url": "https://api.github.com/users/ZhgChgLi/repos",
      "events_url": "https://api.github.com/users/ZhgChgLi/events{/privacy}",
      "received_events_url": "https://api.github.com/users/ZhgChgLi/received_events",
      "type": "Organization",
      "site_admin": false
    },
    "html_url": "https://github.com/ZhgChgLi/ZMarkupParser",
    "description": "ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.",
    "fork": false,
    "url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser",
    "forks_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/forks",
    "keys_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/keys{/key_id}",
    "collaborators_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/collaborators{/collaborator}",
    "teams_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/teams",
    "hooks_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/hooks",
    "issue_events_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/issues/events{/number}",
    "events_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/events",
    "assignees_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/assignees{/user}",
    "branches_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/branches{/branch}",
    "tags_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/tags",
    "blobs_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/git/blobs{/sha}",
    "git_tags_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/git/tags{/sha}",
    "git_refs_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/git/refs{/sha}",
    "trees_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/git/trees{/sha}",
    "statuses_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/languages",
    "stargazers_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/stargazers",
    "contributors_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/contributors",
    "subscribers_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/subscribers",
    "subscription_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/subscription",
    "commits_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/git/commits{/sha}",
    "comments_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/comments{/number}",
    "issue_comment_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/issues/comments{/number}",
    "contents_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/contents/{+path}",
    "compare_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/compare/{base}...{head}",
    "merges_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/merges",
    "archive_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/{archive_format}{/ref}",
    "downloads_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/downloads",
    "issues_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/issues{/number}",
    "pulls_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/pulls{/number}",
    "milestones_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/notifications{?since,all,participating}",
    "labels_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/labels{/name}",
    "releases_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/releases{/id}",
    "deployments_url": "https://api.github.com/repos/ZhgChgLi/ZMarkupParser/deployments",
    "created_at": "2023-02-17T08:41:37Z",
    "updated_at": "2023-08-01T03:42:27Z",
    "pushed_at": "2023-08-01T00:07:41Z",
    "git_url": "git://github.com/ZhgChgLi/ZMarkupParser.git",
    "ssh_url": "git@github.com:ZhgChgLi/ZMarkupParser.git",
    "clone_url": "https://github.com/ZhgChgLi/ZMarkupParser.git",
    "svn_url": "https://github.com/ZhgChgLi/ZMarkupParser",
    "homepage": "https://zhgchg.li",
    "size": 27449,
    "stargazers_count": 187,
    "watchers_count": 187,
    "language": "Swift",
    "has_issues": true,
    "has_projects": true,
    "has_downloads": true,
    "has_wiki": true,
    "has_pages": false,
    "has_discussions": false,
    "forks_count": 10,
    "mirror_url": null,
    "archived": false,
    "disabled": false,
    "open_issues_count": 2,
    "license": {
      "key": "mit",
      "name": "MIT License",
      "spdx_id": "MIT",
      "url": "https://api.github.com/licenses/mit",
      "node_id": "MDc6TGljZW5zZTEz"
    },
    "allow_forking": true,
    "is_template": false,
    "web_commit_signoff_required": false,
    "topics": [
      "cocoapods",
      "html",
      "html-converter",
      "html-parser",
      "html-renderer",
      "ios",
      "nsattributedstring",
      "swift",
      "swift-package",
      "textfield",
      "uikit",
      "uilabel",
      "uitextview"
    ],
    "visibility": "public",
    "forks": 10,
    "open_issues": 2,
    "watchers": 187,
    "default_branch": "main"
  },
  "organization": {
    "login": "ZhgChgLi",
    "id": 83232222,
    "node_id": "MDEyOk9yZ2FuaXphdGlvbjgzMjMyMjIy",
    "url": "https://api.github.com/orgs/ZhgChgLi",
    "repos_url": "https://api.github.com/orgs/ZhgChgLi/repos",
    "events_url": "https://api.github.com/orgs/ZhgChgLi/events",
    "hooks_url": "https://api.github.com/orgs/ZhgChgLi/hooks",
    "issues_url": "https://api.github.com/orgs/ZhgChgLi/issues",
    "members_url": "https://api.github.com/orgs/ZhgChgLi/members{/member}",
    "public_members_url": "https://api.github.com/orgs/ZhgChgLi/public_members{/member}",
    "avatar_url": "https://avatars.githubusercontent.com/u/83232222?v=4",
    "description": "Building a Better World Together."
  },
  "sender": {
    "login": "zhgtest",
    "id": 4601621,
    "node_id": "MDQ6VXNlcjQ2MDE2MjE=",
    "avatar_url": "https://avatars.githubusercontent.com/u/4601621?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/zhgtest",
    "html_url": "https://github.com/zhgtest",
    "followers_url": "https://api.github.com/users/zhgtest/followers",
    "following_url": "https://api.github.com/users/zhgtest/following{/other_user}",
    "gists_url": "https://api.github.com/users/zhgtest/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/zhgtest/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/zhgtest/subscriptions",
    "organizations_url": "https://api.github.com/users/zhgtest/orgs",
    "repos_url": "https://api.github.com/users/zhgtest/repos",
    "events_url": "https://api.github.com/users/zhgtest/events{/privacy}",
    "received_events_url": "https://api.github.com/users/zhgtest/received_events",
    "type": "User",
    "site_admin": false
  }
}

Deployment

After completing the program writing, click on the upper right corner “Deploy” -> “New Deployment”:

Deployment

Select the “Web Application” type on the left:

Web Application Type

Web Application Type

  • Add description: Input any text, I entered “Release.”
  • Who can access: Change it to “Everyone.”
  • Click “Deploy.”

For the first deployment, click on “Grant access”:

Grant Access

Select your current Gmail account from the popped-up account selection:

Select Gmail Account

“Google hasn’t verified this app” will appear because the App we are developing is for personal use and does not require Google’s verification.

Simply click “Advanced” -> “Go to XXX (unsafe)” -> “Allow”:

Advanced

Advanced

Advanced

After completing the deployment, you can find the Request URL in the “Web Application” section on the result page. Click “Copy” and take note of this GAS URL.

Request URL

⚠️️️ By the way, please note that if there are code modifications, you need to update the deployment for the changes to take effect.⚠️

To make the changes in the code effective, click on “Deploy” -> select “Manage Deployments” -> choose the “✏️” in the upper right corner -> select “Create New Version” -> click “Deploy.”

Manage Deployments

Manage Deployments

This will complete the code update deployment.

Github Webhook Configuration

  • Go back to Github
  • We can set up a webhook for Organizations (all Repos inside) or a single Repo to monitor new ⭐️ Stars.

Go to Organizations / Repo -> “Settings” -> find “Webhooks” on the left -> “Add webhook”:

Webhook Settings

Webhook Settings

  • Payload URL **: ** Enter the GAS URL and manually add our own security verification string at the end of the URL ?secret=githubWebhookSecret. For example, if your GAS URL is https://script.google.com/macros/s/XXX/exec and githubWebhookSecret is 123456, then the URL will be: https://script.google.com/macros/s/XXX/exec?secret=123456.
  • **Content type: ** Choose application/json.
  • Which events would you like to trigger this webhook? Choose “Let me select individual events.⚠️️Uncheck “Pushes”. ️️️️⚠️Check “Watches”. Please note that it’s not “Stars” (though Stars also monitor the state of stars clicked, if you use Stars for GAS actions, you will need to adjust accordingly.)
  • Choose “Active”.
  • Click “Add webhook”.
  • Configuration completed.

🚀 Testing

Go back to the Organizations Repo / Repo where the settings were made, click “Star” or un-star and then re-star:

Testing

You will receive a push notification!

Notification

Done! 🎉🎉🎉🎉

Ad Timing!

Like Z Realm's work

Any questions or suggestions are welcome. Please feel free to contact me.

Post converted from Medium by ZMediumToMarkdown.


This post is licensed under CC BY 4.0 by the author.

遊記 2023 東京 5 日自由行

使用 Google Apps Script 三步驟免費建立 Github Repo Star Notifier