github-monitor 1.9.1__tar.gz → 2.1__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github_monitor
3
- Version: 1.9.1
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
- github_monitor is a tool for real-time monitoring of GitHub users' activities, including profile and repository changes.
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
- - detection of account changes
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
- Fallback:
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
@@ -1,6 +1,6 @@
1
1
  # github_monitor
2
2
 
3
- github_monitor is a tool for real-time monitoring of GitHub users' activities, including profile and repository changes.
3
+ OSINT tool for real-time monitoring of GitHub users' activities, including profile and repository changes.
4
4
 
5
5
  <a id="features"></a>
6
6
  ## Features
@@ -12,7 +12,9 @@ github_monitor is a tool for real-time monitoring of GitHub users' activities, i
12
12
  - added/removed starred repositories
13
13
  - added/removed public repositories
14
14
  - changes in user name, email, location, company, bio and blog URL
15
- - detection of account changes
15
+ - changes in profile visibility (public to private and vice versa)
16
+ - detection when a user blocks or unblocks you
17
+ - detection of account metadata changes (such as account update date)
16
18
  - Email notifications for different events (new GitHub events, changed followings, followers, repositories, user name, email, location, company, bio, blog URL etc.)
17
19
  - Saving all user activities with timestamps to the CSV file
18
20
  - Clickable GitHub URLs printed in the console & included in email notifications (repos, PRs, commits, issues, releases etc.)
@@ -141,18 +143,16 @@ Provide the `GITHUB_TOKEN` secret using one of the following methods:
141
143
  - Pass it at runtime with `-t` / `--github-token`
142
144
  - Set it as an [environment variable](#storing-secrets) (e.g. `export GITHUB_TOKEN=...`)
143
145
  - Add it to [.env file](#storing-secrets) (`GITHUB_TOKEN=...`) for persistent use
146
+ - Fallback: hard-code it in the code or config file
144
147
 
145
- Fallback:
146
- - Hard-code it in the code or config file
147
-
148
- 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).
148
+ 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).
149
149
 
150
150
  <a id="github-api-url"></a>
151
151
  ### GitHub API URL
152
152
 
153
153
  By default the tool uses Public Web GitHub API URL: [https://api.github.com](https://api.github.com)
154
154
 
155
- If you want to use GitHub Enterprise API URL then change `GITHUB_API_URL` (or use `-x` flag) to: https://{your_hostname}/api/v3
155
+ If you want to use GitHub Enterprise API URL then change `GITHUB_API_URL` (or use `-x` flag) to: `https://{your_hostname}/api/v3`
156
156
 
157
157
 
158
158
  <a id="events-to-monitor"></a>
@@ -266,6 +266,12 @@ If you want to monitor changes to user's public repositories (e.g. new stargazer
266
266
  github_monitor github_username -j
267
267
  ```
268
268
 
269
+ By default, only user-owned repos are tracked. To include forks and collaborations, set `GET_ALL_REPOS` to `True` or use the `-a` flag:
270
+
271
+ ```sh
272
+ github_monitor github_username -j -a
273
+ ```
274
+
269
275
  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:
270
276
 
271
277
  ```sh
@@ -293,6 +299,12 @@ github_monitor github_username -r
293
299
  <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%"/>
294
300
  </p>
295
301
 
302
+ By default, only user-owned repos are listed. To include forks and collaborations, set `GET_ALL_REPOS` to `True` or use the `-a` flag:
303
+
304
+ ```sh
305
+ github_monitor github_username -r -a
306
+ ```
307
+
296
308
  If you want to display a list of repositories starred by the user then use the `-g` flag:
297
309
 
298
310
  ```sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github_monitor
3
- Version: 1.9.1
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
- github_monitor is a tool for real-time monitoring of GitHub users' activities, including profile and repository changes.
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
- - detection of account changes
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
- Fallback:
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
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
4
- v1.9
4
+ v2.1
5
5
 
6
- OSINT tool implementing real-time tracking of Github users activities including profile and repositories changes:
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 = "1.9.1"
19
+ VERSION = "2.1"
20
20
 
21
21
  # ---------------------------
22
22
  # CONFIGURATION SECTION START
23
23
  # ---------------------------
24
24
 
25
25
  CONFIG_BLOCK = """
26
- # Get your Github personal access token (classic) by visiting:
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 Github API
38
+ # The URL of the GitHub API
40
39
  #
41
- # For Public Web Github use the default: https://api.github.com
42
- # For Github Enterprise change to: https://{your_hostname}/api/v3
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 Github API timestamps are converted accordingly (e.g. 'Europe/Warsaw')
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: [profile changes = {PROFILE_NOTIFICATION}]")
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: [new events = {EVENT_NOTIFICATION}]")
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: [repos changes = {REPO_NOTIFICATION}]")
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: [repos update date = {REPO_UPDATE_DATE_NOTIFICATION}]")
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"* Github timers: [check interval: {display_time(GITHUB_CHECK_INTERVAL)}]")
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"* Github timers: [check interval: {display_time(GITHUB_CHECK_INTERVAL)}]")
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"Github API URL:\t\t{GITHUB_API_URL}")
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
- stargazers_list = [star.login for star in repo.get_stargazers()]
917
- subscribers_list = [subscriber.login for subscriber in repo.get_subscribers()]
918
- forked_repos = [fork.full_name for fork in repo.get_forks()]
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"* Error while processing info for repo '{repo.name}', skipping for now: {e}")
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
- repos_count = g_user.public_repos
958
- repos_list = g_user.get_repos()
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"Github API URL:\t\t{GITHUB_API_URL}")
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
- print(f" - 🌐 URL:\t\t{repo.html_url}")
987
- print(f" - 💻 Language:\t\t{repo.language}")
988
-
989
- print(f"\n - ⭐ Stars:\t\t{repo.stargazers_count}")
990
- print(f" - 🍴 Forks:\t\t{repo.forks_count}")
991
- print(f" - 👓 Watchers:\t\t{repo.subscribers_count}")
992
-
993
- # print(f" - 🐞 Issues+PRs:\t{repo.open_issues_count}")
994
- print(f" - 🐞 Issues:\t\t{issue_count}")
995
- print(f" - 📬 PRs:\t\t{pr_count}")
996
-
997
- print(f"\n - 📝 License:\t\t{repo.license.name if repo.license else 'None'}")
998
- print(f" - 🌿 Branch (default):\t{repo.default_branch}")
999
-
1000
- print(f"\n - 📅 Created:\t\t{get_date_from_ts(repo.created_at)} ({calculate_timespan(int(time.time()), repo.created_at, granularity=2)} ago)")
1001
- print(f" - 🔄 Updated:\t\t{get_date_from_ts(repo.updated_at)} ({calculate_timespan(int(time.time()), repo.updated_at, granularity=2)} ago)")
1002
- print(f" - 🔃 Last push:\t{get_date_from_ts(repo.pushed_at)} ({calculate_timespan(int(time.time()), repo.pushed_at, granularity=2)} ago)")
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"Github API URL:\t\t{GITHUB_API_URL}")
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"Github API URL:\t\t\t{GITHUB_API_URL}")
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"Github user {user} {label.lower()} list changed"
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"Github user {user} {label.lower()} number has changed! ({diff_str}, {old_count} -> {new_count})"
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"Github user {user} {label.lower()} list changed for repo '{repo_name}'!"
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"Github user {user} number of {label.lower()} for repo '{repo_name}' has changed! ({diff_str}, {old_count} -> {new_count})"
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
- # Main function that monitors activity of the specified GitHub user
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
- repos_list = g_user.get_repos()
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"Token belongs to:\t\t{user_myself_name_str}" + f"\n\t\t\t\t[ {user_myself_url} ]" if user_myself_url else "")
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
- # main loop
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
- repos_raw = list(gh_call(g_user.get_repos)())
2035
- repos_count = gh_call(lambda: g_user.public_repos)()
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"Github user {user} bio has changed!"
2060
- 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: ')}"
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"Github user {user} location has changed!"
2084
- 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: ')}"
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"Github user {user} name has changed!"
2108
- 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: ')}"
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"Github user {user} company has changed!"
2132
- 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: ')}"
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"Github user {user} email has changed!"
2156
- 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: ')}"
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"Github user {user} blog URL has changed!"
2180
- 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: ')}"
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"Github user {user} account has been updated! (after {calculate_timespan(account_updated_date, account_updated_date_old, show_seconds=False, granularity=2)})"
2204
- 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: ')}"
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
- repos_list = gh_call(g_user.get_repos)()
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"Github user {user} repo '{r_name}' update date has changed ! (after {calculate_timespan(r_update, r_update_old, show_seconds=False, granularity=2)})"
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"Github user {user} repo '{r_name}' description has changed !"
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 Github events
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"Github user {user} has new {event.type} (repo: {repo_name})"
2370
- 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: ')}"
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"Github Monitoring Tool v{VERSION}\n")
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"* Github polling interval:\t[ {display_time(GITHUB_CHECK_INTERVAL)} ]")
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"* Github API URL:\t\t{GITHUB_API_URL}")
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 Github events:\t{not DO_NOT_MONITOR_GITHUB_EVENTS}")
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 Github user {args.username}"
3086
+ out = f"\nMonitoring GitHub user {args.username}"
2816
3087
  print(out)
2817
3088
  print("-" * len(out))
2818
3089
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "github_monitor"
7
- version = "1.9.1"
7
+ version = "2.1"
8
8
  description = "Tool implementing real-time tracking of Github users activities including profile and repositories changes"
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-or-later"
File without changes
File without changes