github-monitor 1.9.1__py3-none-any.whl → 2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of github-monitor might be problematic. Click here for more details.
- {github_monitor-1.9.1.dist-info → github_monitor-2.1.dist-info}/METADATA +20 -8
- github_monitor-2.1.dist-info/RECORD +7 -0
- github_monitor.py +355 -84
- github_monitor-1.9.1.dist-info/RECORD +0 -7
- {github_monitor-1.9.1.dist-info → github_monitor-2.1.dist-info}/WHEEL +0 -0
- {github_monitor-1.9.1.dist-info → github_monitor-2.1.dist-info}/entry_points.txt +0 -0
- {github_monitor-1.9.1.dist-info → github_monitor-2.1.dist-info}/licenses/LICENSE +0 -0
- {github_monitor-1.9.1.dist-info → github_monitor-2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github_monitor
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.1
|
|
4
4
|
Summary: Tool implementing real-time tracking of Github users activities including profile and repositories changes
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -26,7 +26,7 @@ Dynamic: license-file
|
|
|
26
26
|
|
|
27
27
|
# github_monitor
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
OSINT tool for real-time monitoring of GitHub users' activities, including profile and repository changes.
|
|
30
30
|
|
|
31
31
|
<a id="features"></a>
|
|
32
32
|
## Features
|
|
@@ -38,7 +38,9 @@ github_monitor is a tool for real-time monitoring of GitHub users' activities, i
|
|
|
38
38
|
- added/removed starred repositories
|
|
39
39
|
- added/removed public repositories
|
|
40
40
|
- changes in user name, email, location, company, bio and blog URL
|
|
41
|
-
-
|
|
41
|
+
- changes in profile visibility (public to private and vice versa)
|
|
42
|
+
- detection when a user blocks or unblocks you
|
|
43
|
+
- detection of account metadata changes (such as account update date)
|
|
42
44
|
- Email notifications for different events (new GitHub events, changed followings, followers, repositories, user name, email, location, company, bio, blog URL etc.)
|
|
43
45
|
- Saving all user activities with timestamps to the CSV file
|
|
44
46
|
- Clickable GitHub URLs printed in the console & included in email notifications (repos, PRs, commits, issues, releases etc.)
|
|
@@ -167,18 +169,16 @@ Provide the `GITHUB_TOKEN` secret using one of the following methods:
|
|
|
167
169
|
- Pass it at runtime with `-t` / `--github-token`
|
|
168
170
|
- Set it as an [environment variable](#storing-secrets) (e.g. `export GITHUB_TOKEN=...`)
|
|
169
171
|
- Add it to [.env file](#storing-secrets) (`GITHUB_TOKEN=...`) for persistent use
|
|
172
|
+
- Fallback: hard-code it in the code or config file
|
|
170
173
|
|
|
171
|
-
|
|
172
|
-
- Hard-code it in the code or config file
|
|
173
|
-
|
|
174
|
-
If you store the `GITHUB_TOKEN` in a dotenv file you can update its value and send a `SIGHUP` signal to the process to reload the file with the new token without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
174
|
+
If you store the `GITHUB_TOKEN` in a dotenv file you can update its value and send a `SIGHUP` signal to reload the file with the new token without restarting the tool. More info in [Storing Secrets](#storing-secrets) and [Signal Controls (macOS/Linux/Unix)](#signal-controls-macoslinuxunix).
|
|
175
175
|
|
|
176
176
|
<a id="github-api-url"></a>
|
|
177
177
|
### GitHub API URL
|
|
178
178
|
|
|
179
179
|
By default the tool uses Public Web GitHub API URL: [https://api.github.com](https://api.github.com)
|
|
180
180
|
|
|
181
|
-
If you want to use GitHub Enterprise API URL then change `GITHUB_API_URL` (or use `-x` flag) to: https://{your_hostname}/api/v3
|
|
181
|
+
If you want to use GitHub Enterprise API URL then change `GITHUB_API_URL` (or use `-x` flag) to: `https://{your_hostname}/api/v3`
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
<a id="events-to-monitor"></a>
|
|
@@ -292,6 +292,12 @@ If you want to monitor changes to user's public repositories (e.g. new stargazer
|
|
|
292
292
|
github_monitor github_username -j
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
+
By default, only user-owned repos are tracked. To include forks and collaborations, set `GET_ALL_REPOS` to `True` or use the `-a` flag:
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
github_monitor github_username -j -a
|
|
299
|
+
```
|
|
300
|
+
|
|
295
301
|
If for any reason you do not want to monitor GitHub events for the user (e.g. new pushes, PRs, issues, forks, releases etc.), then use the `-k` flag:
|
|
296
302
|
|
|
297
303
|
```sh
|
|
@@ -319,6 +325,12 @@ github_monitor github_username -r
|
|
|
319
325
|
<img src="https://raw.githubusercontent.com/misiektoja/github_monitor/refs/heads/main/assets/github_list_of_repos.png" alt="github_list_of_repos" width="90%"/>
|
|
320
326
|
</p>
|
|
321
327
|
|
|
328
|
+
By default, only user-owned repos are listed. To include forks and collaborations, set `GET_ALL_REPOS` to `True` or use the `-a` flag:
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
github_monitor github_username -r -a
|
|
332
|
+
```
|
|
333
|
+
|
|
322
334
|
If you want to display a list of repositories starred by the user then use the `-g` flag:
|
|
323
335
|
|
|
324
336
|
```sh
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
github_monitor.py,sha256=TwfAtvASFyS-SFWZryolvv81Q0yoPhz_FBNjw2xXkBA,124098
|
|
2
|
+
github_monitor-2.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
+
github_monitor-2.1.dist-info/METADATA,sha256=kTwOp_S9Gq9baD6Gzl9k8s-0BGwVbU9MWuD3Zi7lkdM,17039
|
|
4
|
+
github_monitor-2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
github_monitor-2.1.dist-info/entry_points.txt,sha256=hV03y00u1L16S5BwBSLQvFsZcL2WGRtjzlrmu9U9SN0,55
|
|
6
|
+
github_monitor-2.1.dist-info/top_level.txt,sha256=HDN2988ydvH9JZT32PushzqrcD05Q5qg960vgHGIaI8,15
|
|
7
|
+
github_monitor-2.1.dist-info/RECORD,,
|
github_monitor.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
|
|
4
|
-
|
|
4
|
+
v2.1
|
|
5
5
|
|
|
6
|
-
OSINT tool implementing real-time tracking of
|
|
6
|
+
OSINT tool implementing real-time tracking of GitHub users activities including profile and repositories changes:
|
|
7
7
|
https://github.com/misiektoja/github_monitor/
|
|
8
8
|
|
|
9
9
|
Python pip3 requirements:
|
|
@@ -16,14 +16,14 @@ tzlocal (optional)
|
|
|
16
16
|
python-dotenv (optional)
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
VERSION = "
|
|
19
|
+
VERSION = "2.1"
|
|
20
20
|
|
|
21
21
|
# ---------------------------
|
|
22
22
|
# CONFIGURATION SECTION START
|
|
23
23
|
# ---------------------------
|
|
24
24
|
|
|
25
25
|
CONFIG_BLOCK = """
|
|
26
|
-
# Get your
|
|
26
|
+
# Get your GitHub personal access token (classic) by visiting:
|
|
27
27
|
# https://github.com/settings/apps
|
|
28
28
|
#
|
|
29
29
|
# Then go to: Personal access tokens -> Tokens (classic) -> Generate new token (classic)
|
|
@@ -32,18 +32,24 @@ CONFIG_BLOCK = """
|
|
|
32
32
|
# - Pass it at runtime with -t / --github-token
|
|
33
33
|
# - Set it as an environment variable (e.g. export GITHUB_TOKEN=...)
|
|
34
34
|
# - Add it to ".env" file (GITHUB_TOKEN=...) for persistent use
|
|
35
|
-
# Fallback:
|
|
36
|
-
# - Hard-code it in the code or config file
|
|
35
|
+
# - Fallback: hard-code it in the code or config file
|
|
37
36
|
GITHUB_TOKEN = "your_github_classic_personal_access_token"
|
|
38
37
|
|
|
39
|
-
# The URL of the
|
|
38
|
+
# The URL of the GitHub API
|
|
40
39
|
#
|
|
41
|
-
# For Public Web
|
|
42
|
-
# For
|
|
40
|
+
# For Public Web GitHub use the default: https://api.github.com
|
|
41
|
+
# For GitHub Enterprise change to: https://{your_hostname}/api/v3
|
|
43
42
|
#
|
|
44
43
|
# Can also be set using the -x flag
|
|
45
44
|
GITHUB_API_URL = "https://api.github.com"
|
|
46
45
|
|
|
46
|
+
# The base URL of the GitHub web interface
|
|
47
|
+
# Required to check if the profile is public or private
|
|
48
|
+
#
|
|
49
|
+
# For public GitHub use the default: https://github.com
|
|
50
|
+
# For GitHub Enterprise change to: https://{your_hostname}
|
|
51
|
+
GITHUB_HTML_URL = "https://github.com"
|
|
52
|
+
|
|
47
53
|
# SMTP settings for sending email notifications
|
|
48
54
|
# If left as-is, no notifications will be sent
|
|
49
55
|
#
|
|
@@ -85,7 +91,7 @@ ERROR_NOTIFICATION = True
|
|
|
85
91
|
# Can also be set using the -c flag
|
|
86
92
|
GITHUB_CHECK_INTERVAL = 1800 # 30 mins
|
|
87
93
|
|
|
88
|
-
# Set your local time zone so that
|
|
94
|
+
# Set your local time zone so that GitHub API timestamps are converted accordingly (e.g. 'Europe/Warsaw')
|
|
89
95
|
# Use this command to list all time zones supported by pytz:
|
|
90
96
|
# python3 -c "import pytz; print('\\n'.join(pytz.all_timezones))"
|
|
91
97
|
# If set to 'Auto', the tool will try to detect your local time zone automatically (requires tzlocal)
|
|
@@ -120,6 +126,13 @@ EVENTS_TO_MONITOR = [
|
|
|
120
126
|
# any events older than the most recent EVENTS_NUMBER will be missed
|
|
121
127
|
EVENTS_NUMBER = 30 # 1 page
|
|
122
128
|
|
|
129
|
+
# If True, fetch all user repos (owned, forks, collaborations); otherwise, fetch only owned repos
|
|
130
|
+
GET_ALL_REPOS = False
|
|
131
|
+
|
|
132
|
+
# Alert about blocked (403 - TOS violation and 451 - DMCA block) repos in the console output (in monitoring mode)
|
|
133
|
+
# In listing mode (-r), blocked repos are always shown
|
|
134
|
+
BLOCKED_REPOS = False
|
|
135
|
+
|
|
123
136
|
# How often to print a "liveness check" message to the output; in seconds
|
|
124
137
|
# Set to 0 to disable
|
|
125
138
|
LIVENESS_CHECK_INTERVAL = 43200 # 12 hours
|
|
@@ -175,6 +188,7 @@ GITHUB_CHECK_SIGNAL_VALUE = 60 # 1 minute
|
|
|
175
188
|
# Do not change values below - modify them in the configuration section or config file instead
|
|
176
189
|
GITHUB_TOKEN = ""
|
|
177
190
|
GITHUB_API_URL = ""
|
|
191
|
+
GITHUB_HTML_URL = ""
|
|
178
192
|
SMTP_HOST = ""
|
|
179
193
|
SMTP_PORT = 0
|
|
180
194
|
SMTP_USER = ""
|
|
@@ -191,6 +205,8 @@ GITHUB_CHECK_INTERVAL = 0
|
|
|
191
205
|
LOCAL_TIMEZONE = ""
|
|
192
206
|
EVENTS_TO_MONITOR = []
|
|
193
207
|
EVENTS_NUMBER = 0
|
|
208
|
+
GET_ALL_REPOS = False
|
|
209
|
+
BLOCKED_REPOS = False
|
|
194
210
|
LIVENESS_CHECK_INTERVAL = 0
|
|
195
211
|
CHECK_INTERNET_URL = ""
|
|
196
212
|
CHECK_INTERNET_TIMEOUT = 0
|
|
@@ -721,7 +737,7 @@ def toggle_profile_changes_notifications_signal_handler(sig, frame):
|
|
|
721
737
|
PROFILE_NOTIFICATION = not PROFILE_NOTIFICATION
|
|
722
738
|
sig_name = signal.Signals(sig).name
|
|
723
739
|
print(f"* Signal {sig_name} received")
|
|
724
|
-
print(f"* Email notifications
|
|
740
|
+
print(f"* Email notifications:\t\t[profile changes = {PROFILE_NOTIFICATION}]")
|
|
725
741
|
print_cur_ts("Timestamp:\t\t\t")
|
|
726
742
|
|
|
727
743
|
|
|
@@ -731,7 +747,7 @@ def toggle_new_events_notifications_signal_handler(sig, frame):
|
|
|
731
747
|
EVENT_NOTIFICATION = not EVENT_NOTIFICATION
|
|
732
748
|
sig_name = signal.Signals(sig).name
|
|
733
749
|
print(f"* Signal {sig_name} received")
|
|
734
|
-
print(f"* Email notifications
|
|
750
|
+
print(f"* Email notifications:\t\t[new events = {EVENT_NOTIFICATION}]")
|
|
735
751
|
print_cur_ts("Timestamp:\t\t\t")
|
|
736
752
|
|
|
737
753
|
|
|
@@ -741,7 +757,7 @@ def toggle_repo_changes_notifications_signal_handler(sig, frame):
|
|
|
741
757
|
REPO_NOTIFICATION = not REPO_NOTIFICATION
|
|
742
758
|
sig_name = signal.Signals(sig).name
|
|
743
759
|
print(f"* Signal {sig_name} received")
|
|
744
|
-
print(f"* Email notifications
|
|
760
|
+
print(f"* Email notifications:\t\t[repos changes = {REPO_NOTIFICATION}]")
|
|
745
761
|
print_cur_ts("Timestamp:\t\t\t")
|
|
746
762
|
|
|
747
763
|
|
|
@@ -751,7 +767,7 @@ def toggle_repo_update_date_changes_notifications_signal_handler(sig, frame):
|
|
|
751
767
|
REPO_UPDATE_DATE_NOTIFICATION = not REPO_UPDATE_DATE_NOTIFICATION
|
|
752
768
|
sig_name = signal.Signals(sig).name
|
|
753
769
|
print(f"* Signal {sig_name} received")
|
|
754
|
-
print(f"* Email notifications
|
|
770
|
+
print(f"* Email notifications:\t\t[repos update date = {REPO_UPDATE_DATE_NOTIFICATION}]")
|
|
755
771
|
print_cur_ts("Timestamp:\t\t\t")
|
|
756
772
|
|
|
757
773
|
|
|
@@ -761,7 +777,7 @@ def increase_check_signal_handler(sig, frame):
|
|
|
761
777
|
GITHUB_CHECK_INTERVAL = GITHUB_CHECK_INTERVAL + GITHUB_CHECK_SIGNAL_VALUE
|
|
762
778
|
sig_name = signal.Signals(sig).name
|
|
763
779
|
print(f"* Signal {sig_name} received")
|
|
764
|
-
print(f"*
|
|
780
|
+
print(f"* GitHub polling interval:\t[ {display_time(GITHUB_CHECK_INTERVAL)} ]")
|
|
765
781
|
print_cur_ts("Timestamp:\t\t\t")
|
|
766
782
|
|
|
767
783
|
|
|
@@ -772,7 +788,7 @@ def decrease_check_signal_handler(sig, frame):
|
|
|
772
788
|
GITHUB_CHECK_INTERVAL = GITHUB_CHECK_INTERVAL - GITHUB_CHECK_SIGNAL_VALUE
|
|
773
789
|
sig_name = signal.Signals(sig).name
|
|
774
790
|
print(f"* Signal {sig_name} received")
|
|
775
|
-
print(f"*
|
|
791
|
+
print(f"* GitHub polling interval:\t[ {display_time(GITHUB_CHECK_INTERVAL)} ]")
|
|
776
792
|
print_cur_ts("Timestamp:\t\t\t")
|
|
777
793
|
|
|
778
794
|
|
|
@@ -865,7 +881,7 @@ def github_print_followers_and_followings(user):
|
|
|
865
881
|
|
|
866
882
|
print(f"\nUsername:\t\t{user_name_str}")
|
|
867
883
|
print(f"User URL:\t\t{user_url}/")
|
|
868
|
-
print(f"
|
|
884
|
+
print(f"GitHub API URL:\t\t{GITHUB_API_URL}")
|
|
869
885
|
print(f"Local timezone:\t\t{LOCAL_TIMEZONE}")
|
|
870
886
|
|
|
871
887
|
print(f"\nFollowers:\t\t{followers_count}")
|
|
@@ -903,6 +919,7 @@ def github_print_followers_and_followings(user):
|
|
|
903
919
|
|
|
904
920
|
# Processes items from all passed repositories and returns a list of dictionaries
|
|
905
921
|
def github_process_repos(repos_list):
|
|
922
|
+
import logging
|
|
906
923
|
list_of_repos = []
|
|
907
924
|
stargazers_list = []
|
|
908
925
|
subscribers_list = []
|
|
@@ -913,9 +930,24 @@ def github_process_repos(repos_list):
|
|
|
913
930
|
try:
|
|
914
931
|
repo_created_date = repo.created_at
|
|
915
932
|
repo_updated_date = repo.updated_at
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
933
|
+
|
|
934
|
+
github_logger = logging.getLogger('github')
|
|
935
|
+
original_level = github_logger.level
|
|
936
|
+
github_logger.setLevel(logging.ERROR)
|
|
937
|
+
|
|
938
|
+
try:
|
|
939
|
+
stargazers_list = [star.login for star in repo.get_stargazers()]
|
|
940
|
+
subscribers_list = [subscriber.login for subscriber in repo.get_subscribers()]
|
|
941
|
+
forked_repos = [fork.full_name for fork in repo.get_forks()]
|
|
942
|
+
except GithubException as e:
|
|
943
|
+
if e.status in [403, 451]:
|
|
944
|
+
if BLOCKED_REPOS:
|
|
945
|
+
print(f"* Repo '{repo.name}' is blocked, skipping for now: {e}")
|
|
946
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
947
|
+
continue
|
|
948
|
+
raise
|
|
949
|
+
finally:
|
|
950
|
+
github_logger.setLevel(original_level)
|
|
919
951
|
|
|
920
952
|
issues = list(repo.get_issues(state='open'))
|
|
921
953
|
pulls = list(repo.get_pulls(state='open'))
|
|
@@ -928,8 +960,20 @@ def github_process_repos(repos_list):
|
|
|
928
960
|
pr_list = [f"#{pr.number} {pr.title} ({pr.user.login}) [ {pr.html_url} ]" for pr in pulls]
|
|
929
961
|
|
|
930
962
|
list_of_repos.append({"name": repo.name, "descr": repo.description, "is_fork": repo.fork, "forks": repo.forks_count, "stars": repo.stargazers_count, "subscribers": repo.subscribers_count, "url": repo.html_url, "language": repo.language, "date": repo_created_date, "update_date": repo_updated_date, "stargazers_list": stargazers_list, "forked_repos": forked_repos, "subscribers_list": subscribers_list, "issues": issue_count, "pulls": pr_count, "issues_list": issues_list, "pulls_list": pr_list})
|
|
963
|
+
|
|
964
|
+
except GithubException as e:
|
|
965
|
+
# Skip TOS-blocked (403) and legally blocked (451) repositories
|
|
966
|
+
if e.status in [403, 451]:
|
|
967
|
+
if BLOCKED_REPOS:
|
|
968
|
+
print(f"* Repo '{repo.name}' is blocked, skipping for now: {e}")
|
|
969
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
970
|
+
continue
|
|
971
|
+
else:
|
|
972
|
+
print(f"* Cannot process repo '{repo.name}', skipping for now: {e}")
|
|
973
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
974
|
+
continue
|
|
931
975
|
except Exception as e:
|
|
932
|
-
print(f"*
|
|
976
|
+
print(f"* Cannot process repo '{repo.name}', skipping for now: {e}")
|
|
933
977
|
print_cur_ts("Timestamp:\t\t\t")
|
|
934
978
|
continue
|
|
935
979
|
|
|
@@ -938,6 +982,7 @@ def github_process_repos(repos_list):
|
|
|
938
982
|
|
|
939
983
|
# Prints a list of public repositories for a GitHub user (-r)
|
|
940
984
|
def github_print_repos(user):
|
|
985
|
+
import logging
|
|
941
986
|
user_name_str = user
|
|
942
987
|
user_url = "-"
|
|
943
988
|
repos_count = 0
|
|
@@ -954,8 +999,12 @@ def github_print_repos(user):
|
|
|
954
999
|
user_name = g_user.name
|
|
955
1000
|
user_url = g_user.html_url
|
|
956
1001
|
|
|
957
|
-
|
|
958
|
-
|
|
1002
|
+
if GET_ALL_REPOS:
|
|
1003
|
+
repos_list = g_user.get_repos()
|
|
1004
|
+
repos_count = g_user.public_repos
|
|
1005
|
+
else:
|
|
1006
|
+
repos_list = [repo for repo in g_user.get_repos(type='owner') if not repo.fork and repo.owner.login == user_login]
|
|
1007
|
+
repos_count = len(repos_list)
|
|
959
1008
|
|
|
960
1009
|
user_name_str = user_login
|
|
961
1010
|
if user_name:
|
|
@@ -965,7 +1014,8 @@ def github_print_repos(user):
|
|
|
965
1014
|
|
|
966
1015
|
print(f"\nUsername:\t\t{user_name_str}")
|
|
967
1016
|
print(f"User URL:\t\t{user_url}/")
|
|
968
|
-
print(f"
|
|
1017
|
+
print(f"GitHub API URL:\t\t{GITHUB_API_URL}")
|
|
1018
|
+
print(f"Owned repos only:\t{not GET_ALL_REPOS}")
|
|
969
1019
|
print(f"Local timezone:\t\t{LOCAL_TIMEZONE}")
|
|
970
1020
|
|
|
971
1021
|
print(f"\nRepositories:\t\t{repos_count}\n")
|
|
@@ -976,6 +1026,10 @@ def github_print_repos(user):
|
|
|
976
1026
|
for repo in repos_list:
|
|
977
1027
|
print(f"🔸 {repo.name} {'(fork)' if repo.fork else ''} \n")
|
|
978
1028
|
|
|
1029
|
+
github_logger = logging.getLogger('github')
|
|
1030
|
+
original_level = github_logger.level
|
|
1031
|
+
github_logger.setLevel(logging.ERROR)
|
|
1032
|
+
|
|
979
1033
|
try:
|
|
980
1034
|
pr_count = repo.get_pulls(state='open').totalCount
|
|
981
1035
|
issue_count = repo.open_issues_count - pr_count
|
|
@@ -983,26 +1037,36 @@ def github_print_repos(user):
|
|
|
983
1037
|
pr_count = "?"
|
|
984
1038
|
issue_count = "?"
|
|
985
1039
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1040
|
+
try:
|
|
1041
|
+
print(f" - 🌐 URL:\t\t{repo.html_url}")
|
|
1042
|
+
print(f" - 💻 Language:\t\t{repo.language}")
|
|
1043
|
+
|
|
1044
|
+
print(f"\n - ⭐ Stars:\t\t{repo.stargazers_count}")
|
|
1045
|
+
print(f" - 🍴 Forks:\t\t{repo.forks_count}")
|
|
1046
|
+
print(f" - 👓 Watchers:\t\t{repo.subscribers_count}")
|
|
1047
|
+
|
|
1048
|
+
# print(f" - 🐞 Issues+PRs:\t{repo.open_issues_count}")
|
|
1049
|
+
print(f" - 🐞 Issues:\t\t{issue_count}")
|
|
1050
|
+
print(f" - 📬 PRs:\t\t{pr_count}")
|
|
1051
|
+
|
|
1052
|
+
print(f"\n - 📝 License:\t\t{repo.license.name if repo.license else 'None'}")
|
|
1053
|
+
print(f" - 🌿 Branch (default):\t{repo.default_branch}")
|
|
1054
|
+
|
|
1055
|
+
print(f"\n - 📅 Created:\t\t{get_date_from_ts(repo.created_at)} ({calculate_timespan(int(time.time()), repo.created_at, granularity=2)} ago)")
|
|
1056
|
+
print(f" - 🔄 Updated:\t\t{get_date_from_ts(repo.updated_at)} ({calculate_timespan(int(time.time()), repo.updated_at, granularity=2)} ago)")
|
|
1057
|
+
print(f" - 🔃 Last push:\t{get_date_from_ts(repo.pushed_at)} ({calculate_timespan(int(time.time()), repo.pushed_at, granularity=2)} ago)")
|
|
1058
|
+
|
|
1059
|
+
if repo.description:
|
|
1060
|
+
print(f"\n - 📝 Desc:\t\t{repo.description}")
|
|
1061
|
+
except GithubException as e:
|
|
1062
|
+
# Inform about TOS-blocked (403) and legally blocked (451) repositories
|
|
1063
|
+
if e.status in [403, 451]:
|
|
1064
|
+
print(f"\n* Repo '{repo.name}' is blocked: {e}")
|
|
1065
|
+
print("─" * HORIZONTAL_LINE2)
|
|
1066
|
+
continue
|
|
1067
|
+
finally:
|
|
1068
|
+
github_logger.setLevel(original_level)
|
|
1003
1069
|
|
|
1004
|
-
if repo.description:
|
|
1005
|
-
print(f"\n - 📝 Desc:\t\t{repo.description}")
|
|
1006
1070
|
print("─" * HORIZONTAL_LINE2)
|
|
1007
1071
|
except Exception as e:
|
|
1008
1072
|
raise RuntimeError(f"Cannot fetch user's repositories list: {e}")
|
|
@@ -1039,7 +1103,7 @@ def github_print_starred_repos(user):
|
|
|
1039
1103
|
|
|
1040
1104
|
print(f"\nUsername:\t\t{user_name_str}")
|
|
1041
1105
|
print(f"User URL:\t\t{user_url}/")
|
|
1042
|
-
print(f"
|
|
1106
|
+
print(f"GitHub API URL:\t\t{GITHUB_API_URL}")
|
|
1043
1107
|
print(f"Local timezone:\t\t{LOCAL_TIMEZONE}")
|
|
1044
1108
|
|
|
1045
1109
|
print(f"\nRepos starred by user:\t{starred_count}")
|
|
@@ -1557,7 +1621,7 @@ def github_list_events(user, number, csv_file_name):
|
|
|
1557
1621
|
|
|
1558
1622
|
print(f"Username:\t\t\t{user_name_str}")
|
|
1559
1623
|
print(f"User URL:\t\t\t{user_url}/")
|
|
1560
|
-
print(f"
|
|
1624
|
+
print(f"GitHub API URL:\t\t\t{GITHUB_API_URL}")
|
|
1561
1625
|
if csv_file_name:
|
|
1562
1626
|
print(f"CSV export enabled:\t\t{bool(csv_file_name)}" + (f" ({csv_file_name})" if csv_file_name else ""))
|
|
1563
1627
|
print(f"Local timezone:\t\t\t{LOCAL_TIMEZONE}")
|
|
@@ -1664,12 +1728,12 @@ def handle_profile_change(label, count_old, count_new, list_old, raw_list, user,
|
|
|
1664
1728
|
print()
|
|
1665
1729
|
|
|
1666
1730
|
if diff == 0:
|
|
1667
|
-
m_subject = f"
|
|
1731
|
+
m_subject = f"GitHub user {user} {label.lower()} list changed"
|
|
1668
1732
|
m_body = (f"{label} list changed {label_context} user {user}\n"
|
|
1669
1733
|
f"{removed_mbody}{removed_list_str}{added_mbody}{added_list_str}\n"
|
|
1670
1734
|
f"Check interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}")
|
|
1671
1735
|
else:
|
|
1672
|
-
m_subject = f"
|
|
1736
|
+
m_subject = f"GitHub user {user} {label.lower()} number has changed! ({diff_str}, {old_count} -> {new_count})"
|
|
1673
1737
|
m_body = (f"{label} number changed {label_context} user {user} from {old_count} to {new_count} ({diff_str})\n"
|
|
1674
1738
|
f"{removed_mbody}{removed_list_str}{added_mbody}{added_list_str}\n"
|
|
1675
1739
|
f"Check interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}")
|
|
@@ -1752,12 +1816,12 @@ def check_repo_list_changes(count_old, count_new, list_old, list_new, label, rep
|
|
|
1752
1816
|
print()
|
|
1753
1817
|
|
|
1754
1818
|
if diff == 0:
|
|
1755
|
-
m_subject = f"
|
|
1819
|
+
m_subject = f"GitHub user {user} {label.lower()} list changed for repo '{repo_name}'!"
|
|
1756
1820
|
m_body = (f"* Repo '{repo_name}': {label.lower()} list changed\n"
|
|
1757
1821
|
f"* Repo URL: {repo_url}\n{removed_mbody}{removed_list_str}{added_mbody}{added_list_str}\n"
|
|
1758
1822
|
f"Check interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}")
|
|
1759
1823
|
else:
|
|
1760
|
-
m_subject = f"
|
|
1824
|
+
m_subject = f"GitHub user {user} number of {label.lower()} for repo '{repo_name}' has changed! ({diff_str}, {old_count} -> {new_count})"
|
|
1761
1825
|
m_body = (f"* Repo '{repo_name}': number of {label.lower()} changed from {old_count} to {new_count} ({diff_str})\n"
|
|
1762
1826
|
f"* Repo URL: {repo_url}\n{removed_mbody}{removed_list_str}{added_mbody}{added_list_str}\n"
|
|
1763
1827
|
f"Check interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}")
|
|
@@ -1807,7 +1871,119 @@ def resolve_executable(path):
|
|
|
1807
1871
|
raise FileNotFoundError(f"Could not find executable '{path}'")
|
|
1808
1872
|
|
|
1809
1873
|
|
|
1810
|
-
#
|
|
1874
|
+
# Checks if the authenticated user (token's owner) is blocked by user
|
|
1875
|
+
def is_blocked_by(user):
|
|
1876
|
+
try:
|
|
1877
|
+
|
|
1878
|
+
headers = {
|
|
1879
|
+
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
|
1880
|
+
"Accept": "application/vnd.github+json",
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
response = req.get(f"{GITHUB_API_URL}/user", headers=headers, timeout=15)
|
|
1884
|
+
if response.status_code != 200:
|
|
1885
|
+
return False
|
|
1886
|
+
me_login = response.json().get("login", "").lower()
|
|
1887
|
+
if user.lower() == me_login:
|
|
1888
|
+
return False
|
|
1889
|
+
|
|
1890
|
+
graphql_endpoint = GITHUB_API_URL.rstrip("/") + "/graphql"
|
|
1891
|
+
query = """
|
|
1892
|
+
query($login: String!) {
|
|
1893
|
+
user(login: $login) {
|
|
1894
|
+
viewerCanFollow
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
"""
|
|
1898
|
+
payload = {"query": query, "variables": {"login": user}}
|
|
1899
|
+
response_graphql = req.post(graphql_endpoint, json=payload, headers=headers, timeout=15)
|
|
1900
|
+
|
|
1901
|
+
if response_graphql.status_code == 404:
|
|
1902
|
+
return False
|
|
1903
|
+
|
|
1904
|
+
if not response_graphql.ok:
|
|
1905
|
+
return False
|
|
1906
|
+
|
|
1907
|
+
data = response_graphql.json()
|
|
1908
|
+
can_follow = (data.get("data", {}).get("user", {}).get("viewerCanFollow", True))
|
|
1909
|
+
return not bool(can_follow)
|
|
1910
|
+
|
|
1911
|
+
except Exception:
|
|
1912
|
+
return False
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
# Return the total number of repositories the user has starred (faster than via PyGithub)
|
|
1916
|
+
def get_starred_count(user):
|
|
1917
|
+
try:
|
|
1918
|
+
|
|
1919
|
+
headers = {
|
|
1920
|
+
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
|
1921
|
+
"Accept": "application/vnd.github+json",
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
graphql_endpoint = f"{GITHUB_API_URL.rstrip('/')}/graphql"
|
|
1925
|
+
query = """
|
|
1926
|
+
query($login:String!){
|
|
1927
|
+
user(login:$login){
|
|
1928
|
+
starredRepositories{
|
|
1929
|
+
totalCount
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
"""
|
|
1934
|
+
payload = {"query": query, "variables": {"login": user}}
|
|
1935
|
+
response = req.post(graphql_endpoint, json=payload, headers=headers, timeout=15)
|
|
1936
|
+
|
|
1937
|
+
if not response.ok:
|
|
1938
|
+
return 0
|
|
1939
|
+
|
|
1940
|
+
data = response.json()
|
|
1941
|
+
|
|
1942
|
+
return (data.get("data", {}).get("user", {}).get("starredRepositories", {}).get("totalCount", 0))
|
|
1943
|
+
|
|
1944
|
+
except Exception:
|
|
1945
|
+
return 0
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
# Returns True if the user's GitHub page shows "activity is private"
|
|
1949
|
+
def has_private_banner(user):
|
|
1950
|
+
try:
|
|
1951
|
+
url = f"{GITHUB_HTML_URL.rstrip('/')}/{user}"
|
|
1952
|
+
r = req.get(url, timeout=15)
|
|
1953
|
+
return r.ok and "activity is private" in r.text.lower()
|
|
1954
|
+
except Exception:
|
|
1955
|
+
return False
|
|
1956
|
+
|
|
1957
|
+
|
|
1958
|
+
# Returns True if the user's GitHub profile is public
|
|
1959
|
+
def is_profile_public(g: Github, user, new_account_days=30):
|
|
1960
|
+
|
|
1961
|
+
if has_private_banner(user):
|
|
1962
|
+
return False
|
|
1963
|
+
|
|
1964
|
+
try:
|
|
1965
|
+
u = g.get_user(user)
|
|
1966
|
+
|
|
1967
|
+
if any([
|
|
1968
|
+
u.followers > 0,
|
|
1969
|
+
u.following > 0,
|
|
1970
|
+
get_starred_count(user) > 0,
|
|
1971
|
+
]):
|
|
1972
|
+
return True
|
|
1973
|
+
|
|
1974
|
+
try:
|
|
1975
|
+
events_iter = iter(u.get_events())
|
|
1976
|
+
next(events_iter)
|
|
1977
|
+
return True
|
|
1978
|
+
except (StopIteration, GithubException):
|
|
1979
|
+
pass
|
|
1980
|
+
|
|
1981
|
+
except GithubException:
|
|
1982
|
+
pass
|
|
1983
|
+
|
|
1984
|
+
return False
|
|
1985
|
+
|
|
1986
|
+
# Monitors activity of the specified GitHub user
|
|
1811
1987
|
def github_monitor_user(user, csv_file_name):
|
|
1812
1988
|
|
|
1813
1989
|
try:
|
|
@@ -1824,6 +2000,10 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1824
2000
|
events = []
|
|
1825
2001
|
repos_list = []
|
|
1826
2002
|
event_date: datetime | None = None
|
|
2003
|
+
blocked = None
|
|
2004
|
+
public = False
|
|
2005
|
+
|
|
2006
|
+
print("Sneaking into GitHub like a ninja ...")
|
|
1827
2007
|
|
|
1828
2008
|
try:
|
|
1829
2009
|
auth = Auth.Token(GITHUB_TOKEN)
|
|
@@ -1847,21 +2027,29 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1847
2027
|
|
|
1848
2028
|
followers_count = g_user.followers
|
|
1849
2029
|
followings_count = g_user.following
|
|
1850
|
-
repos_count = g_user.public_repos
|
|
1851
2030
|
|
|
1852
2031
|
followers_list = g_user.get_followers()
|
|
1853
2032
|
followings_list = g_user.get_following()
|
|
1854
|
-
|
|
2033
|
+
|
|
2034
|
+
if GET_ALL_REPOS:
|
|
2035
|
+
repos_list = g_user.get_repos()
|
|
2036
|
+
repos_count = g_user.public_repos
|
|
2037
|
+
else:
|
|
2038
|
+
repos_list = [repo for repo in g_user.get_repos(type='owner') if not repo.fork and repo.owner.login == user_login]
|
|
2039
|
+
repos_count = len(repos_list)
|
|
1855
2040
|
|
|
1856
2041
|
starred_list = g_user.get_starred()
|
|
1857
2042
|
starred_count = starred_list.totalCount
|
|
1858
2043
|
|
|
2044
|
+
public = is_profile_public(g, user)
|
|
2045
|
+
blocked = is_blocked_by(user) if public else None
|
|
2046
|
+
|
|
1859
2047
|
if not DO_NOT_MONITOR_GITHUB_EVENTS:
|
|
1860
2048
|
events = list(islice(g_user.get_events(), EVENTS_NUMBER))
|
|
1861
2049
|
available_events = len(events)
|
|
1862
2050
|
|
|
1863
2051
|
except Exception as e:
|
|
1864
|
-
print(f"* Error: {e}")
|
|
2052
|
+
print(f"\n* Error: {e}")
|
|
1865
2053
|
sys.exit(1)
|
|
1866
2054
|
|
|
1867
2055
|
last_event_id = 0
|
|
@@ -1879,7 +2067,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1879
2067
|
if last_event_id:
|
|
1880
2068
|
last_event_ts = newest.created_at
|
|
1881
2069
|
except Exception as e:
|
|
1882
|
-
print(f"* Cannot get event IDs / timestamps: {e}\n")
|
|
2070
|
+
print(f"\n* Cannot get event IDs / timestamps: {e}\n")
|
|
1883
2071
|
pass
|
|
1884
2072
|
|
|
1885
2073
|
followers_old_count = followers_count
|
|
@@ -1893,6 +2081,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1893
2081
|
company_old = company
|
|
1894
2082
|
email_old = email
|
|
1895
2083
|
blog_old = blog
|
|
2084
|
+
blocked_old = blocked
|
|
2085
|
+
public_old = public
|
|
1896
2086
|
|
|
1897
2087
|
last_event_id_old = last_event_id
|
|
1898
2088
|
last_event_ts_old = last_event_ts
|
|
@@ -1902,7 +2092,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1902
2092
|
if user_myself_name:
|
|
1903
2093
|
user_myself_name_str += f" ({user_myself_name})"
|
|
1904
2094
|
|
|
1905
|
-
print(f"
|
|
2095
|
+
print(f"\nToken belongs to:\t\t{user_myself_name_str}" + f"\n\t\t\t\t[ {user_myself_url} ]" if user_myself_url else "")
|
|
1906
2096
|
|
|
1907
2097
|
user_name_str = user_login
|
|
1908
2098
|
if user_name:
|
|
@@ -1923,6 +2113,9 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1923
2113
|
if blog:
|
|
1924
2114
|
print(f"Blog URL:\t\t\t{blog}")
|
|
1925
2115
|
|
|
2116
|
+
print(f"\nPublic profile:\t\t\t{'Yes' if public else 'No'}")
|
|
2117
|
+
print(f"Blocked by the user:\t\t{'Unknown' if blocked is None else ('Yes' if blocked else 'No')}")
|
|
2118
|
+
|
|
1926
2119
|
print(f"\nAccount creation date:\t\t{get_date_from_ts(account_created_date)} ({calculate_timespan(int(time.time()), account_created_date, show_seconds=False)} ago)")
|
|
1927
2120
|
print(f"Account updated date:\t\t{get_date_from_ts(account_updated_date)} ({calculate_timespan(int(time.time()), account_updated_date, show_seconds=False)} ago)")
|
|
1928
2121
|
account_updated_date_old = account_updated_date
|
|
@@ -1931,6 +2124,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1931
2124
|
print(f"Followings:\t\t\t{followings_count}")
|
|
1932
2125
|
print(f"Repositories:\t\t\t{repos_count}")
|
|
1933
2126
|
print(f"Starred repos:\t\t\t{starred_count}")
|
|
2127
|
+
|
|
1934
2128
|
if not DO_NOT_MONITOR_GITHUB_EVENTS:
|
|
1935
2129
|
print(f"Available events:\t\t{available_events}{'+' if available_events == EVENTS_NUMBER else ''}")
|
|
1936
2130
|
|
|
@@ -1983,7 +2177,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
1983
2177
|
alive_counter = 0
|
|
1984
2178
|
email_sent = False
|
|
1985
2179
|
|
|
1986
|
-
#
|
|
2180
|
+
# Primary loop
|
|
1987
2181
|
while True:
|
|
1988
2182
|
|
|
1989
2183
|
try:
|
|
@@ -2031,8 +2225,13 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2031
2225
|
followers_old, followers_old_count = handle_profile_change("Followers", followers_old_count, followers_count, followers_old, followers_raw, user, csv_file_name, field="login")
|
|
2032
2226
|
|
|
2033
2227
|
# Changed public repositories
|
|
2034
|
-
|
|
2035
|
-
|
|
2228
|
+
if GET_ALL_REPOS:
|
|
2229
|
+
repos_raw = list(gh_call(g_user.get_repos)())
|
|
2230
|
+
repos_count = gh_call(lambda: g_user.public_repos)()
|
|
2231
|
+
else:
|
|
2232
|
+
repos_raw = list(gh_call(lambda: [repo for repo in g_user.get_repos(type='owner') if not repo.fork and repo.owner.login == user_login])())
|
|
2233
|
+
repos_count = len(repos_raw)
|
|
2234
|
+
|
|
2036
2235
|
if repos_raw is not None and repos_count is not None:
|
|
2037
2236
|
repos_old, repos_old_count = handle_profile_change("Repos", repos_old_count, repos_count, repos_old, repos_raw, user, csv_file_name, field="name")
|
|
2038
2237
|
|
|
@@ -2056,8 +2255,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2056
2255
|
except Exception as e:
|
|
2057
2256
|
print(f"* Error: {e}")
|
|
2058
2257
|
|
|
2059
|
-
m_subject = f"
|
|
2060
|
-
m_body = f"
|
|
2258
|
+
m_subject = f"GitHub user {user} bio has changed!"
|
|
2259
|
+
m_body = f"GitHub user {user} bio has changed\n\nOld bio:\n\n{bio_old}\n\nNew bio:\n\n{bio}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2061
2260
|
|
|
2062
2261
|
if PROFILE_NOTIFICATION:
|
|
2063
2262
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2080,8 +2279,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2080
2279
|
except Exception as e:
|
|
2081
2280
|
print(f"* Error: {e}")
|
|
2082
2281
|
|
|
2083
|
-
m_subject = f"
|
|
2084
|
-
m_body = f"
|
|
2282
|
+
m_subject = f"GitHub user {user} location has changed!"
|
|
2283
|
+
m_body = f"GitHub user {user} location has changed\n\nOld location: {location_old}\n\nNew location: {location}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2085
2284
|
|
|
2086
2285
|
if PROFILE_NOTIFICATION:
|
|
2087
2286
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2104,8 +2303,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2104
2303
|
except Exception as e:
|
|
2105
2304
|
print(f"* Error: {e}")
|
|
2106
2305
|
|
|
2107
|
-
m_subject = f"
|
|
2108
|
-
m_body = f"
|
|
2306
|
+
m_subject = f"GitHub user {user} name has changed!"
|
|
2307
|
+
m_body = f"GitHub user {user} name has changed\n\nOld user name: {user_name_old}\n\nNew user name: {user_name}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2109
2308
|
|
|
2110
2309
|
if PROFILE_NOTIFICATION:
|
|
2111
2310
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2128,8 +2327,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2128
2327
|
except Exception as e:
|
|
2129
2328
|
print(f"* Error: {e}")
|
|
2130
2329
|
|
|
2131
|
-
m_subject = f"
|
|
2132
|
-
m_body = f"
|
|
2330
|
+
m_subject = f"GitHub user {user} company has changed!"
|
|
2331
|
+
m_body = f"GitHub user {user} company has changed\n\nOld company: {company_old}\n\nNew company: {company}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2133
2332
|
|
|
2134
2333
|
if PROFILE_NOTIFICATION:
|
|
2135
2334
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2152,8 +2351,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2152
2351
|
except Exception as e:
|
|
2153
2352
|
print(f"* Error: {e}")
|
|
2154
2353
|
|
|
2155
|
-
m_subject = f"
|
|
2156
|
-
m_body = f"
|
|
2354
|
+
m_subject = f"GitHub user {user} email has changed!"
|
|
2355
|
+
m_body = f"GitHub user {user} email has changed\n\nOld email: {email_old}\n\nNew email: {email}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2157
2356
|
|
|
2158
2357
|
if PROFILE_NOTIFICATION:
|
|
2159
2358
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2176,8 +2375,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2176
2375
|
except Exception as e:
|
|
2177
2376
|
print(f"* Error: {e}")
|
|
2178
2377
|
|
|
2179
|
-
m_subject = f"
|
|
2180
|
-
m_body = f"
|
|
2378
|
+
m_subject = f"GitHub user {user} blog URL has changed!"
|
|
2379
|
+
m_body = f"GitHub user {user} blog URL has changed\n\nOld blog URL: {blog_old}\n\nNew blog URL: {blog}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2181
2380
|
|
|
2182
2381
|
if PROFILE_NOTIFICATION:
|
|
2183
2382
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2200,8 +2399,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2200
2399
|
except Exception as e:
|
|
2201
2400
|
print(f"* Error: {e}")
|
|
2202
2401
|
|
|
2203
|
-
m_subject = f"
|
|
2204
|
-
m_body = f"
|
|
2402
|
+
m_subject = f"GitHub user {user} account has been updated! (after {calculate_timespan(account_updated_date, account_updated_date_old, show_seconds=False, granularity=2)})"
|
|
2403
|
+
m_body = f"GitHub user {user} account has been updated (after {calculate_timespan(account_updated_date, account_updated_date_old, show_seconds=False, granularity=2)})\n\nOld account update date: {get_date_from_ts(account_updated_date_old)}\n\nNew account update date: {get_date_from_ts(account_updated_date)}\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2205
2404
|
|
|
2206
2405
|
if PROFILE_NOTIFICATION:
|
|
2207
2406
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2211,11 +2410,72 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2211
2410
|
print(f"Check interval:\t\t\t{display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
2212
2411
|
print_cur_ts("Timestamp:\t\t\t")
|
|
2213
2412
|
|
|
2413
|
+
# Profile visibility changed
|
|
2414
|
+
public = is_profile_public(g, user)
|
|
2415
|
+
if public != public_old:
|
|
2416
|
+
|
|
2417
|
+
def _get_profile_status(public):
|
|
2418
|
+
return "public" if public else "private"
|
|
2419
|
+
|
|
2420
|
+
print(f"* User {user} has changed profile visibility to '{_get_profile_status(public)}' !\n")
|
|
2421
|
+
|
|
2422
|
+
try:
|
|
2423
|
+
if csv_file_name:
|
|
2424
|
+
write_csv_entry(csv_file_name, now_local_naive(), "Profile Visibility", user, _get_profile_status(public_old), _get_profile_status(public))
|
|
2425
|
+
except Exception as e:
|
|
2426
|
+
print(f"* Error: {e}")
|
|
2427
|
+
|
|
2428
|
+
m_subject = f"GitHub user {user} has changed profile visibility to '{_get_profile_status(public)}' !"
|
|
2429
|
+
m_body = f"GitHub user {user} has changed profile visibility to '{_get_profile_status(public)}' !\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2430
|
+
|
|
2431
|
+
if PROFILE_NOTIFICATION:
|
|
2432
|
+
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2433
|
+
send_email(m_subject, m_body, "", SMTP_SSL)
|
|
2434
|
+
|
|
2435
|
+
public_old = public
|
|
2436
|
+
print(f"Check interval:\t\t\t{display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
2437
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
2438
|
+
|
|
2439
|
+
# Blocked status changed
|
|
2440
|
+
blocked = is_blocked_by(user) if public else None
|
|
2441
|
+
|
|
2442
|
+
if blocked is not None and blocked_old is None:
|
|
2443
|
+
blocked_old = blocked
|
|
2444
|
+
|
|
2445
|
+
elif None not in (blocked_old, blocked) and blocked != blocked_old:
|
|
2446
|
+
|
|
2447
|
+
def _get_blocked_status(blocked, public):
|
|
2448
|
+
return 'Unknown' if blocked is None else ('Yes' if blocked else 'No')
|
|
2449
|
+
|
|
2450
|
+
print(f"* User {user} has {'blocked' if blocked else 'unblocked'} you!\n")
|
|
2451
|
+
|
|
2452
|
+
try:
|
|
2453
|
+
if csv_file_name:
|
|
2454
|
+
write_csv_entry(csv_file_name, now_local_naive(), "Block Status", user, _get_blocked_status(blocked_old, public), _get_blocked_status(blocked, public))
|
|
2455
|
+
except Exception as e:
|
|
2456
|
+
print(f"* Error: {e}")
|
|
2457
|
+
|
|
2458
|
+
m_subject = f"GitHub user {user} has {'blocked' if blocked else 'unblocked'} you!"
|
|
2459
|
+
m_body = f"GitHub user {user} has {'blocked' if blocked else 'unblocked'} you!\n\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2460
|
+
|
|
2461
|
+
if PROFILE_NOTIFICATION:
|
|
2462
|
+
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
2463
|
+
send_email(m_subject, m_body, "", SMTP_SSL)
|
|
2464
|
+
|
|
2465
|
+
blocked_old = blocked
|
|
2466
|
+
print(f"Check interval:\t\t\t{display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
2467
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
2468
|
+
|
|
2214
2469
|
list_of_repos = []
|
|
2215
2470
|
|
|
2216
2471
|
# Changed repos details
|
|
2217
2472
|
if TRACK_REPOS_CHANGES:
|
|
2218
|
-
|
|
2473
|
+
|
|
2474
|
+
if GET_ALL_REPOS:
|
|
2475
|
+
repos_list = gh_call(g_user.get_repos)()
|
|
2476
|
+
else:
|
|
2477
|
+
repos_list = gh_call(lambda: [repo for repo in g_user.get_repos(type='owner') if not repo.fork and repo.owner.login == user_login])()
|
|
2478
|
+
|
|
2219
2479
|
if repos_list is not None:
|
|
2220
2480
|
try:
|
|
2221
2481
|
list_of_repos = github_process_repos(repos_list)
|
|
@@ -2269,7 +2529,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2269
2529
|
write_csv_entry(csv_file_name, now_local_naive(), "Repo Update Date", r_name, convert_to_local_naive(r_update_old), convert_to_local_naive(r_update))
|
|
2270
2530
|
except Exception as e:
|
|
2271
2531
|
print(f"* Error: {e}")
|
|
2272
|
-
m_subject = f"
|
|
2532
|
+
m_subject = f"GitHub user {user} repo '{r_name}' update date has changed ! (after {calculate_timespan(r_update, r_update_old, show_seconds=False, granularity=2)})"
|
|
2273
2533
|
m_body = f"{r_message}\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2274
2534
|
if REPO_UPDATE_DATE_NOTIFICATION:
|
|
2275
2535
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2301,7 +2561,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2301
2561
|
write_csv_entry(csv_file_name, now_local_naive(), "Repo Description", r_name, r_descr_old, r_descr)
|
|
2302
2562
|
except Exception as e:
|
|
2303
2563
|
print(f"* Error: {e}")
|
|
2304
|
-
m_subject = f"
|
|
2564
|
+
m_subject = f"GitHub user {user} repo '{r_name}' description has changed !"
|
|
2305
2565
|
m_body = f"{r_message}\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2306
2566
|
if REPO_NOTIFICATION:
|
|
2307
2567
|
print(f"Sending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2311,7 +2571,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2311
2571
|
|
|
2312
2572
|
list_of_repos_old = list_of_repos
|
|
2313
2573
|
|
|
2314
|
-
# New
|
|
2574
|
+
# New GitHub events
|
|
2315
2575
|
if not DO_NOT_MONITOR_GITHUB_EVENTS:
|
|
2316
2576
|
events = list(gh_call(lambda: list(islice(g_user.get_events(), EVENTS_NUMBER)))())
|
|
2317
2577
|
if events is not None:
|
|
@@ -2366,8 +2626,8 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2366
2626
|
except Exception as e:
|
|
2367
2627
|
print(f"* Error: {e}")
|
|
2368
2628
|
|
|
2369
|
-
m_subject = f"
|
|
2370
|
-
m_body = f"
|
|
2629
|
+
m_subject = f"GitHub user {user} has new {event.type} (repo: {repo_name})"
|
|
2630
|
+
m_body = f"GitHub user {user} has new {event.type} event\n\n{event_text}\nCheck interval: {display_time(GITHUB_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - GITHUB_CHECK_INTERVAL, int(time.time()), short=True)}){get_cur_ts(nl_ch + 'Timestamp: ')}"
|
|
2371
2631
|
|
|
2372
2632
|
if EVENT_NOTIFICATION:
|
|
2373
2633
|
print(f"\nSending email notification to {RECEIVER_EMAIL}")
|
|
@@ -2392,7 +2652,7 @@ def github_monitor_user(user, csv_file_name):
|
|
|
2392
2652
|
|
|
2393
2653
|
|
|
2394
2654
|
def main():
|
|
2395
|
-
global CLI_CONFIG_PATH, DOTENV_FILE, LOCAL_TIMEZONE, LIVENESS_CHECK_COUNTER, GITHUB_TOKEN, GITHUB_API_URL, CSV_FILE, DISABLE_LOGGING, GITHUB_LOGFILE, PROFILE_NOTIFICATION, EVENT_NOTIFICATION, REPO_NOTIFICATION, REPO_UPDATE_DATE_NOTIFICATION, ERROR_NOTIFICATION, GITHUB_CHECK_INTERVAL, SMTP_PASSWORD, stdout_bck, DO_NOT_MONITOR_GITHUB_EVENTS, TRACK_REPOS_CHANGES
|
|
2655
|
+
global CLI_CONFIG_PATH, DOTENV_FILE, LOCAL_TIMEZONE, LIVENESS_CHECK_COUNTER, GITHUB_TOKEN, GITHUB_API_URL, CSV_FILE, DISABLE_LOGGING, GITHUB_LOGFILE, PROFILE_NOTIFICATION, EVENT_NOTIFICATION, REPO_NOTIFICATION, REPO_UPDATE_DATE_NOTIFICATION, ERROR_NOTIFICATION, GITHUB_CHECK_INTERVAL, SMTP_PASSWORD, stdout_bck, DO_NOT_MONITOR_GITHUB_EVENTS, TRACK_REPOS_CHANGES, GET_ALL_REPOS
|
|
2396
2656
|
|
|
2397
2657
|
if "--generate-config" in sys.argv:
|
|
2398
2658
|
print(CONFIG_BLOCK.strip("\n"))
|
|
@@ -2409,7 +2669,7 @@ def main():
|
|
|
2409
2669
|
|
|
2410
2670
|
clear_screen(CLEAR_SCREEN)
|
|
2411
2671
|
|
|
2412
|
-
print(f"
|
|
2672
|
+
print(f"GitHub Monitoring Tool v{VERSION}\n")
|
|
2413
2673
|
|
|
2414
2674
|
parser = argparse.ArgumentParser(
|
|
2415
2675
|
prog="github_monitor",
|
|
@@ -2577,6 +2837,13 @@ def main():
|
|
|
2577
2837
|
default=None,
|
|
2578
2838
|
help="Disable event monitoring"
|
|
2579
2839
|
)
|
|
2840
|
+
opts.add_argument(
|
|
2841
|
+
"-a", "--get-all-repos",
|
|
2842
|
+
dest="get_all_repos",
|
|
2843
|
+
action="store_true",
|
|
2844
|
+
default=None,
|
|
2845
|
+
help="Fetch all user repos (owned, forks, collaborations)"
|
|
2846
|
+
)
|
|
2580
2847
|
opts.add_argument(
|
|
2581
2848
|
"-b", "--csv-file",
|
|
2582
2849
|
dest="csv_file",
|
|
@@ -2694,6 +2961,9 @@ def main():
|
|
|
2694
2961
|
print("* Error: GITHUB_API_URL (-x / --github_url) value is empty")
|
|
2695
2962
|
sys.exit(1)
|
|
2696
2963
|
|
|
2964
|
+
if args.get_all_repos is True:
|
|
2965
|
+
GET_ALL_REPOS = True
|
|
2966
|
+
|
|
2697
2967
|
if args.list_followers_and_followings:
|
|
2698
2968
|
try:
|
|
2699
2969
|
github_print_followers_and_followings(args.username)
|
|
@@ -2800,11 +3070,12 @@ def main():
|
|
|
2800
3070
|
REPO_UPDATE_DATE_NOTIFICATION = False
|
|
2801
3071
|
ERROR_NOTIFICATION = False
|
|
2802
3072
|
|
|
2803
|
-
print(f"*
|
|
3073
|
+
print(f"* GitHub polling interval:\t[ {display_time(GITHUB_CHECK_INTERVAL)} ]")
|
|
2804
3074
|
print(f"* Email notifications:\t\t[profile changes = {PROFILE_NOTIFICATION}] [new events = {EVENT_NOTIFICATION}]\n*\t\t\t\t[repos changes = {REPO_NOTIFICATION}] [repos update date = {REPO_UPDATE_DATE_NOTIFICATION}]\n*\t\t\t\t[errors = {ERROR_NOTIFICATION}]")
|
|
2805
|
-
print(f"*
|
|
3075
|
+
print(f"* GitHub API URL:\t\t{GITHUB_API_URL}")
|
|
2806
3076
|
print(f"* Track repos changes:\t\t{TRACK_REPOS_CHANGES}")
|
|
2807
|
-
print(f"* Monitor
|
|
3077
|
+
print(f"* Monitor GitHub events:\t{not DO_NOT_MONITOR_GITHUB_EVENTS}")
|
|
3078
|
+
print(f"* Get owned repos only:\t\t{not GET_ALL_REPOS}")
|
|
2808
3079
|
print(f"* Liveness check:\t\t{bool(LIVENESS_CHECK_INTERVAL)}" + (f" ({display_time(LIVENESS_CHECK_INTERVAL)})" if LIVENESS_CHECK_INTERVAL else ""))
|
|
2809
3080
|
print(f"* CSV logging enabled:\t\t{bool(CSV_FILE)}" + (f" ({CSV_FILE})" if CSV_FILE else ""))
|
|
2810
3081
|
print(f"* Output logging enabled:\t{not DISABLE_LOGGING}" + (f" ({FINAL_LOG_PATH})" if not DISABLE_LOGGING else ""))
|
|
@@ -2812,7 +3083,7 @@ def main():
|
|
|
2812
3083
|
print(f"* Dotenv file:\t\t\t{env_path or 'None'}")
|
|
2813
3084
|
print(f"* Local timezone:\t\t{LOCAL_TIMEZONE}")
|
|
2814
3085
|
|
|
2815
|
-
out = f"\nMonitoring
|
|
3086
|
+
out = f"\nMonitoring GitHub user {args.username}"
|
|
2816
3087
|
print(out)
|
|
2817
3088
|
print("-" * len(out))
|
|
2818
3089
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
github_monitor.py,sha256=F1MAvwW8lwzUKCKUlV4Xexs8OW5qjDxsAYN43XeDQew,113740
|
|
2
|
-
github_monitor-1.9.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
3
|
-
github_monitor-1.9.1.dist-info/METADATA,sha256=u3ake7RKz2uzVPbl2KAkYew8ouT8_sD9GiuFzZe4_Ak,16541
|
|
4
|
-
github_monitor-1.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
github_monitor-1.9.1.dist-info/entry_points.txt,sha256=hV03y00u1L16S5BwBSLQvFsZcL2WGRtjzlrmu9U9SN0,55
|
|
6
|
-
github_monitor-1.9.1.dist-info/top_level.txt,sha256=HDN2988ydvH9JZT32PushzqrcD05Q5qg960vgHGIaI8,15
|
|
7
|
-
github_monitor-1.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|