Create a Free GitHub Repo Star Notifier in Three Steps Using Google Apps Script
Write GAS to connect with GitHub Webhook to forward star notifications to Line
ℹ️ℹ️ℹ️ The following content is translated by OpenAI.
Click here to view the original Chinese version. | 點此查看本文中文版
Create a Free GitHub Repo Star Notifier in Three Steps Using Google Apps Script
Write GAS to connect with GitHub Webhook to forward star notifications to Line
Introduction
As a maintainer of an open-source project, I am not motivated by money or fame, but rather by a sense of vanity; every time I see a new ⭐️ star, I can’t help but feel delighted. It reassures me that the time and effort I put into my project is genuinely being used and is helpful to others facing similar issues.
Thus, my observation of ⭐️ stars has become somewhat obsessive; I often refresh GitHub to check if the ⭐️ star count has increased. I wondered if there was a more proactive way to receive notifications when someone stars my repository, eliminating the need for manual tracking.
Existing Tools
First, I considered looking for existing tools to achieve this. I searched the GitHub Marketplace and found several tools created by talented developers.
I tried a few of them, but the results were disappointing. Some were no longer operational, while others only sent notifications after every 5/10/20 stars (I’m just a small project; I’d be thrilled with just 1 new ⭐️ 😝). Some tools only sent email notifications, but I wanted to use SNS notifications.
Moreover, I felt uneasy about installing an app just for “vanity,” fearing potential security risks.
The iOS GitHub app and third-party apps like GitTrends do not support this feature either.
Build Your Own GitHub Repo Star Notifier
Given the above, we can actually create our own GitHub Repo Star Notifier quickly and for free using Google Apps Script.
2024/10/12 Update
⚠️⚠️⚠️
Due to Line Notify shutting down on 2025/04/01, please refer to my latest article “Quickly Transition Line Notify to Telegram Bot Notifications in 10 Minutes“ to use Telegram for notification functionality.
Preparation
This article uses Line as the notification medium. If you want to use other messaging software for notifications, you can ask ChatGPT how to implement it.
Ask ChatGPT how to implement Line Notify
lineToken
:
- Go to Line Notify
- After logging into your Line account, scroll down to find the “Generate access token (For developers)” section.
- Click “Generate token.”
- Token Name: Enter the desired name for your bot, which will appear before the message (e.g.,
GitHub Repo Notifier: XXXX
). - Choose where to send the message: I selected
1-on-1 chat with LINE Notify
to send messages to myself via the official LINE Notify bot. - Click “Generate token.”
- Select “Copy.”
- Make sure to note down the Token, as you will need to regenerate it if forgotten; it cannot be viewed again.
githubWebhookSecret
:
- Go to Random.org to generate a random string.
- Copy & note down this random string.
We will use this string as a request verification medium between GitHub Webhook and Google Apps Script.
Due to GAS limitations, it is not possible to obtain
Headers
content indoPost(e)
, so the standard GitHub Webhook verification method cannot be used. We can only manually use?secret=
Query for string matching verification.
Create Google Apps Script
Go to Google Apps Script, and click on the “+” icon in the top left corner to create a new project.
Click on “Untitled project” in the top left corner to rename the project.
I named the project My-Github-Repo-Notifier
for easy identification later.
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
// Constant variables
const lineToken = 'XXXX';
// Generate your line notify bot token: https://notify-bot.line.me/my/
const githubWebhookSecret = "XXXXX";
// Generate your 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
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!");
}
// Combine message
const message = makeMessageString(content);
// Send message, can also change to send 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)
// Unable to obtain Headers content
// Therefore, the standard GitHub Webhook verification method (https://docs.github.com/en/webhooks-and-events/webhooks/securing-your-webhooks) cannot be used
// Only manual matching with ?secret=XXX is possible
function verifyGitHubWebhook(e) {
if (e.parameter["secret"] === githubWebhookSecret) {
return true
} else {
return false
}
}
// -- Send Message --
// Line
// Other messaging methods can be asked from 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 copied from the previous steps.
Supplementary Information: The data that GitHub Webhook sends when someone stars 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 code, click on the top right corner “Deploy” -> “New deployment”:
On the left side, select the type “Web app”:
- Add a description: Enter anything; I put “
Release
“. - Who has access: Please change to “
Anyone
“. - Click “Deploy.”
For the first deployment, you will need to click “Authorize access”:
After the account selection pop-up appears, choose your current Gmail account:
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

The message "Google hasn’t verified this app" appears because the app we are developing is for personal use and does not require verification by Google.
Simply click on "Advanced" -> "Go to XXX (unsafe)" -> "Allow":



After deployment, you can find the Request URL on the results page under "Web App." Click "Copy" and make a note of this GAS URL.

**⚠️️️ Note: If the code has been modified, you need to update the deployment for the changes to take effect. ⚠️**
To make the changes effective, click on "Deploy" in the upper right corner -> select "Manage Deployments" -> choose the pencil icon in the upper right corner -> select "Create New Version" -> click "Deploy."


This completes the code update deployment.
#### Github Webhook Settings
- Go back to [Github](https://github.com/){:target="_blank"}
- We can set up a Webhook for Organizations (all repos inside) or for a single repo to listen for new ⭐️ stars.
Navigate to Organizations / Repo -> "Settings" -> find "Webhooks" on the left -> "Add webhook":


- Payload URL **:** Enter the `GAS URL` and manually append our own security verification string `?secret=githubWebhookSecret` to the end of the URL.
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:** Select `application/json`
- **Which events would you like to trigger this webhook?**
**Select "Let me select individual events."**
**⚠️️ Uncheck "Pushes"**
**️️️️⚠️ Check "Watches," please note it is not "Stars" (but ** Stars also monitor the status of star clicks, so if using Stars for GAS actions, adjustments are needed **).**
- Select "Active"
- Click "Add webhook"
- Complete the setup
#### 🚀 Testing
Go back to the configured Organizations Repo / Repo and click "Star" or un-star and then re-"Star":

You will receive a push notification!

That's a wrap! 🎉🎉🎉🎉
### Promotions
[](https://github.com/ZhgChgLi/ZMarkupParser){:target="_blank"}
[](https://github.com/ZhgChgLi/ZReviewTender){:target="_blank"}
If you have any questions or feedback, feel free to [contact me](https://www.zhgchg.li/contact){:target="_blank"}.
This article was first published on Medium ➡️ Click Here
Automatically converted and synchronized using ZMediumToMarkdown and Medium-to-jekyll-starter.