spotify-profile-monitor 3.1__tar.gz → 3.2__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.
- {spotify_profile_monitor-3.1/spotify_profile_monitor.egg-info → spotify_profile_monitor-3.2}/PKG-INFO +64 -27
- spotify_profile_monitor-3.1/PKG-INFO → spotify_profile_monitor-3.2/README.md +53 -54
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/pyproject.toml +11 -2
- spotify_profile_monitor-3.1/README.md → spotify_profile_monitor-3.2/spotify_profile_monitor.egg-info/PKG-INFO +91 -25
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.py +153 -16
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/LICENSE +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/setup.cfg +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.egg-info/SOURCES.txt +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.egg-info/dependency_links.txt +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.egg-info/entry_points.txt +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.egg-info/requires.txt +0 -0
- {spotify_profile_monitor-3.1 → spotify_profile_monitor-3.2}/spotify_profile_monitor.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 3.2
|
|
4
|
+
Summary: Track Spotify profile, follower and playlist changes in real time
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/misiektoja/spotify_profile_monitor
|
|
@@ -9,6 +9,15 @@ Project-URL: Source, https://github.com/misiektoja/spotify_profile_monitor
|
|
|
9
9
|
Project-URL: Changelog, https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md
|
|
10
10
|
Keywords: spotify,monitoring,tracking,real-time,playlists,osint
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
21
|
Classifier: Operating System :: OS Independent
|
|
13
22
|
Classifier: Operating System :: Microsoft :: Windows
|
|
14
23
|
Classifier: Operating System :: POSIX :: Linux
|
|
@@ -29,38 +38,61 @@ Dynamic: license-file
|
|
|
29
38
|
|
|
30
39
|
# spotify_profile_monitor
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
<p align="left">
|
|
42
|
+
<img src="https://img.shields.io/github/v/release/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="GitHub Release" />
|
|
43
|
+
<img src="https://img.shields.io/pypi/v/spotify_profile_monitor?style=flat-square&color=teal" alt="PyPI Version" />
|
|
44
|
+
<img src="https://img.shields.io/github/stars/misiektoja/spotify_profile_monitor?style=flat-square&color=magenta" alt="GitHub Stars" />
|
|
45
|
+
<img src="https://img.shields.io/badge/python-3.6+-blueviolet?style=flat-square" alt="Python Versions" />
|
|
46
|
+
<img src="https://img.shields.io/github/license/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="License" />
|
|
47
|
+
<img src="https://img.shields.io/github/last-commit/misiektoja/spotify_profile_monitor?style=flat-square&color=green" alt="Last Commit" />
|
|
48
|
+
<img src="https://img.shields.io/badge/maintenance-active-brightgreen?style=flat-square" alt="Maintenance" />
|
|
49
|
+
</p>
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
Powerful Spotify tool for real-time tracking of profile changes, playlist updates, follower growth, collaborators and more - delivered straight to your terminal or inbox.
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
### 🚀 Quick Install
|
|
54
|
+
```sh
|
|
55
|
+
pip install spotify_profile_monitor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="90%"/>
|
|
60
|
+
</p>
|
|
37
61
|
|
|
38
62
|
<a id="features"></a>
|
|
39
63
|
## Features
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- **
|
|
65
|
+
### 👤 Profile Monitoring
|
|
66
|
+
- **Real-time tracking**: Monitor Spotify user activities and profile changes.
|
|
67
|
+
- **Social Network**: Detect addition/removal of **followings** and **followers**.
|
|
68
|
+
- **Identity Changes**: Track **profile picture** and **username** changes.
|
|
69
|
+
|
|
70
|
+
### 📜 Playlist Tracking
|
|
71
|
+
- **Content Updates**: Monitor addition/removal of **tracks in playlists**.
|
|
72
|
+
- **Collaborator Info**: Identify who added which track in **collaborative playlists**.
|
|
73
|
+
- **Social Proof**: Monitor **likes** and **collaborators** count for playlists.
|
|
74
|
+
- **Metadata**: Track **name** and **description** changes.
|
|
75
|
+
|
|
76
|
+
### 📊 Advanced Tools
|
|
77
|
+
- **Deep Insights**: Display detailed info about users, followers and followings.
|
|
78
|
+
- **Historical Data**: View **recently played artists** and **search for users** by name.
|
|
79
|
+
- **Export Power**: Display and export tracks from any playlist (including **Liked Songs**).
|
|
80
|
+
- **Global Search**: Instant links to **Spotify, YouTube Music, Apple Music, Tidal, lyrics** and more.
|
|
81
|
+
|
|
82
|
+
### 🔔 Smart Interactions
|
|
83
|
+
- **Instant Alerts**: Detailed **Email notifications** for all profile and playlist changes.
|
|
84
|
+
- **Visual Reports**: Attach changed profile pictures directly to emails.
|
|
85
|
+
- **Terminal Graphics**: Display profile pictures right in your terminal (via `imgcat`).
|
|
86
|
+
|
|
87
|
+
### ⚙️ Power Features
|
|
88
|
+
- **Auth Flexibility**: Hybrid support for `sp_dc` cookie, Desktop Client and OAuth.
|
|
89
|
+
- **CSV Logging**: Save all changes with full timestamps to a CSV file.
|
|
90
|
+
- **Flexible Config**: Support for files, dotenv and environment variables.
|
|
91
|
+
- **Signal Control**: Manage the running script via system signals (`SIGHUP`, `USR1`, etc.).
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
✨ If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
94
|
+
|
|
95
|
+
🛠️ If you're looking for debug tools to get Spotify Web Player access tokens and extract secret keys: [click here](#debugging-tools)
|
|
64
96
|
|
|
65
97
|
<a id="table-of-contents"></a>
|
|
66
98
|
## Table of Contents
|
|
@@ -185,10 +217,15 @@ Most settings can be configured via command-line arguments.
|
|
|
185
217
|
If you want to have it stored persistently, generate a default config template and save it to a file named `spotify_profile_monitor.conf`:
|
|
186
218
|
|
|
187
219
|
```sh
|
|
220
|
+
# On macOS, Linux or Windows Command Prompt (cmd.exe)
|
|
188
221
|
spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
189
222
|
|
|
223
|
+
# On Windows PowerShell (recommended to avoid encoding issues)
|
|
224
|
+
spotify_profile_monitor --generate-config spotify_profile_monitor.conf
|
|
190
225
|
```
|
|
191
226
|
|
|
227
|
+
> **IMPORTANT**: On **Windows PowerShell**, using redirection (`>`) can cause the file to be encoded in UTF-16, which will lead to "null bytes" errors when running the tool. It is highly recommended to provide the filename directly as an argument to `--generate-config` to ensure UTF-8 encoding.
|
|
228
|
+
|
|
192
229
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
193
230
|
|
|
194
231
|
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
@@ -1,66 +1,60 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: spotify_profile_monitor
|
|
3
|
-
Version: 3.1
|
|
4
|
-
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
|
-
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
|
-
License-Expression: GPL-3.0-or-later
|
|
7
|
-
Project-URL: Homepage, https://github.com/misiektoja/spotify_profile_monitor
|
|
8
|
-
Project-URL: Source, https://github.com/misiektoja/spotify_profile_monitor
|
|
9
|
-
Project-URL: Changelog, https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md
|
|
10
|
-
Keywords: spotify,monitoring,tracking,real-time,playlists,osint
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
|
-
Requires-Python: >=3.6
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE
|
|
19
|
-
Requires-Dist: requests>=2.0
|
|
20
|
-
Requires-Dist: python-dateutil>=2.8
|
|
21
|
-
Requires-Dist: urllib3>=2.0.7
|
|
22
|
-
Requires-Dist: pyotp>=2.9.0
|
|
23
|
-
Requires-Dist: pytz>=2020.1
|
|
24
|
-
Requires-Dist: tzlocal>=4.0
|
|
25
|
-
Requires-Dist: python-dotenv>=0.19
|
|
26
|
-
Requires-Dist: spotipy>=2.24.0
|
|
27
|
-
Requires-Dist: wcwidth>=0.2.7
|
|
28
|
-
Dynamic: license-file
|
|
29
|
-
|
|
30
1
|
# spotify_profile_monitor
|
|
31
2
|
|
|
32
|
-
|
|
3
|
+
<p align="left">
|
|
4
|
+
<img src="https://img.shields.io/github/v/release/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="GitHub Release" />
|
|
5
|
+
<img src="https://img.shields.io/pypi/v/spotify_profile_monitor?style=flat-square&color=teal" alt="PyPI Version" />
|
|
6
|
+
<img src="https://img.shields.io/github/stars/misiektoja/spotify_profile_monitor?style=flat-square&color=magenta" alt="GitHub Stars" />
|
|
7
|
+
<img src="https://img.shields.io/badge/python-3.6+-blueviolet?style=flat-square" alt="Python Versions" />
|
|
8
|
+
<img src="https://img.shields.io/github/license/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="License" />
|
|
9
|
+
<img src="https://img.shields.io/github/last-commit/misiektoja/spotify_profile_monitor?style=flat-square&color=green" alt="Last Commit" />
|
|
10
|
+
<img src="https://img.shields.io/badge/maintenance-active-brightgreen?style=flat-square" alt="Maintenance" />
|
|
11
|
+
</p>
|
|
33
12
|
|
|
34
|
-
|
|
13
|
+
Powerful Spotify tool for real-time tracking of profile changes, playlist updates, follower growth, collaborators and more - delivered straight to your terminal or inbox.
|
|
35
14
|
|
|
36
|
-
|
|
15
|
+
### 🚀 Quick Install
|
|
16
|
+
```sh
|
|
17
|
+
pip install spotify_profile_monitor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="90%"/>
|
|
22
|
+
</p>
|
|
37
23
|
|
|
38
24
|
<a id="features"></a>
|
|
39
25
|
## Features
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- **
|
|
27
|
+
### 👤 Profile Monitoring
|
|
28
|
+
- **Real-time tracking**: Monitor Spotify user activities and profile changes.
|
|
29
|
+
- **Social Network**: Detect addition/removal of **followings** and **followers**.
|
|
30
|
+
- **Identity Changes**: Track **profile picture** and **username** changes.
|
|
31
|
+
|
|
32
|
+
### 📜 Playlist Tracking
|
|
33
|
+
- **Content Updates**: Monitor addition/removal of **tracks in playlists**.
|
|
34
|
+
- **Collaborator Info**: Identify who added which track in **collaborative playlists**.
|
|
35
|
+
- **Social Proof**: Monitor **likes** and **collaborators** count for playlists.
|
|
36
|
+
- **Metadata**: Track **name** and **description** changes.
|
|
37
|
+
|
|
38
|
+
### 📊 Advanced Tools
|
|
39
|
+
- **Deep Insights**: Display detailed info about users, followers and followings.
|
|
40
|
+
- **Historical Data**: View **recently played artists** and **search for users** by name.
|
|
41
|
+
- **Export Power**: Display and export tracks from any playlist (including **Liked Songs**).
|
|
42
|
+
- **Global Search**: Instant links to **Spotify, YouTube Music, Apple Music, Tidal, lyrics** and more.
|
|
43
|
+
|
|
44
|
+
### 🔔 Smart Interactions
|
|
45
|
+
- **Instant Alerts**: Detailed **Email notifications** for all profile and playlist changes.
|
|
46
|
+
- **Visual Reports**: Attach changed profile pictures directly to emails.
|
|
47
|
+
- **Terminal Graphics**: Display profile pictures right in your terminal (via `imgcat`).
|
|
48
|
+
|
|
49
|
+
### ⚙️ Power Features
|
|
50
|
+
- **Auth Flexibility**: Hybrid support for `sp_dc` cookie, Desktop Client and OAuth.
|
|
51
|
+
- **CSV Logging**: Save all changes with full timestamps to a CSV file.
|
|
52
|
+
- **Flexible Config**: Support for files, dotenv and environment variables.
|
|
53
|
+
- **Signal Control**: Manage the running script via system signals (`SIGHUP`, `USR1`, etc.).
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
✨ If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
56
|
+
|
|
57
|
+
🛠️ If you're looking for debug tools to get Spotify Web Player access tokens and extract secret keys: [click here](#debugging-tools)
|
|
64
58
|
|
|
65
59
|
<a id="table-of-contents"></a>
|
|
66
60
|
## Table of Contents
|
|
@@ -185,10 +179,15 @@ Most settings can be configured via command-line arguments.
|
|
|
185
179
|
If you want to have it stored persistently, generate a default config template and save it to a file named `spotify_profile_monitor.conf`:
|
|
186
180
|
|
|
187
181
|
```sh
|
|
182
|
+
# On macOS, Linux or Windows Command Prompt (cmd.exe)
|
|
188
183
|
spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
189
184
|
|
|
185
|
+
# On Windows PowerShell (recommended to avoid encoding issues)
|
|
186
|
+
spotify_profile_monitor --generate-config spotify_profile_monitor.conf
|
|
190
187
|
```
|
|
191
188
|
|
|
189
|
+
> **IMPORTANT**: On **Windows PowerShell**, using redirection (`>`) can cause the file to be encoded in UTF-16, which will lead to "null bytes" errors when running the tool. It is highly recommended to provide the filename directly as an argument to `--generate-config` to ensure UTF-8 encoding.
|
|
190
|
+
|
|
192
191
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
193
192
|
|
|
194
193
|
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spotify_profile_monitor"
|
|
7
|
-
version = "3.
|
|
8
|
-
description = "
|
|
7
|
+
version = "3.2"
|
|
8
|
+
description = "Track Spotify profile, follower and playlist changes in real time"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
11
11
|
license-files = ["LICEN[CS]E*"]
|
|
@@ -24,6 +24,15 @@ dependencies = [
|
|
|
24
24
|
]
|
|
25
25
|
classifiers = [
|
|
26
26
|
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.6",
|
|
28
|
+
"Programming Language :: Python :: 3.7",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Programming Language :: Python :: 3.14",
|
|
27
36
|
"Operating System :: OS Independent",
|
|
28
37
|
"Operating System :: Microsoft :: Windows",
|
|
29
38
|
"Operating System :: POSIX :: Linux",
|
|
@@ -1,37 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spotify_profile_monitor
|
|
3
|
+
Version: 3.2
|
|
4
|
+
Summary: Track Spotify profile, follower and playlist changes in real time
|
|
5
|
+
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/misiektoja/spotify_profile_monitor
|
|
8
|
+
Project-URL: Source, https://github.com/misiektoja/spotify_profile_monitor
|
|
9
|
+
Project-URL: Changelog, https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md
|
|
10
|
+
Keywords: spotify,monitoring,tracking,real-time,playlists,osint
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
23
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
24
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
25
|
+
Requires-Python: >=3.6
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests>=2.0
|
|
29
|
+
Requires-Dist: python-dateutil>=2.8
|
|
30
|
+
Requires-Dist: urllib3>=2.0.7
|
|
31
|
+
Requires-Dist: pyotp>=2.9.0
|
|
32
|
+
Requires-Dist: pytz>=2020.1
|
|
33
|
+
Requires-Dist: tzlocal>=4.0
|
|
34
|
+
Requires-Dist: python-dotenv>=0.19
|
|
35
|
+
Requires-Dist: spotipy>=2.24.0
|
|
36
|
+
Requires-Dist: wcwidth>=0.2.7
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
1
39
|
# spotify_profile_monitor
|
|
2
40
|
|
|
3
|
-
|
|
41
|
+
<p align="left">
|
|
42
|
+
<img src="https://img.shields.io/github/v/release/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="GitHub Release" />
|
|
43
|
+
<img src="https://img.shields.io/pypi/v/spotify_profile_monitor?style=flat-square&color=teal" alt="PyPI Version" />
|
|
44
|
+
<img src="https://img.shields.io/github/stars/misiektoja/spotify_profile_monitor?style=flat-square&color=magenta" alt="GitHub Stars" />
|
|
45
|
+
<img src="https://img.shields.io/badge/python-3.6+-blueviolet?style=flat-square" alt="Python Versions" />
|
|
46
|
+
<img src="https://img.shields.io/github/license/misiektoja/spotify_profile_monitor?style=flat-square&color=blue" alt="License" />
|
|
47
|
+
<img src="https://img.shields.io/github/last-commit/misiektoja/spotify_profile_monitor?style=flat-square&color=green" alt="Last Commit" />
|
|
48
|
+
<img src="https://img.shields.io/badge/maintenance-active-brightgreen?style=flat-square" alt="Maintenance" />
|
|
49
|
+
</p>
|
|
4
50
|
|
|
5
|
-
|
|
51
|
+
Powerful Spotify tool for real-time tracking of profile changes, playlist updates, follower growth, collaborators and more - delivered straight to your terminal or inbox.
|
|
6
52
|
|
|
7
|
-
|
|
53
|
+
### 🚀 Quick Install
|
|
54
|
+
```sh
|
|
55
|
+
pip install spotify_profile_monitor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="90%"/>
|
|
60
|
+
</p>
|
|
8
61
|
|
|
9
62
|
<a id="features"></a>
|
|
10
63
|
## Features
|
|
11
64
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **
|
|
65
|
+
### 👤 Profile Monitoring
|
|
66
|
+
- **Real-time tracking**: Monitor Spotify user activities and profile changes.
|
|
67
|
+
- **Social Network**: Detect addition/removal of **followings** and **followers**.
|
|
68
|
+
- **Identity Changes**: Track **profile picture** and **username** changes.
|
|
69
|
+
|
|
70
|
+
### 📜 Playlist Tracking
|
|
71
|
+
- **Content Updates**: Monitor addition/removal of **tracks in playlists**.
|
|
72
|
+
- **Collaborator Info**: Identify who added which track in **collaborative playlists**.
|
|
73
|
+
- **Social Proof**: Monitor **likes** and **collaborators** count for playlists.
|
|
74
|
+
- **Metadata**: Track **name** and **description** changes.
|
|
75
|
+
|
|
76
|
+
### 📊 Advanced Tools
|
|
77
|
+
- **Deep Insights**: Display detailed info about users, followers and followings.
|
|
78
|
+
- **Historical Data**: View **recently played artists** and **search for users** by name.
|
|
79
|
+
- **Export Power**: Display and export tracks from any playlist (including **Liked Songs**).
|
|
80
|
+
- **Global Search**: Instant links to **Spotify, YouTube Music, Apple Music, Tidal, lyrics** and more.
|
|
81
|
+
|
|
82
|
+
### 🔔 Smart Interactions
|
|
83
|
+
- **Instant Alerts**: Detailed **Email notifications** for all profile and playlist changes.
|
|
84
|
+
- **Visual Reports**: Attach changed profile pictures directly to emails.
|
|
85
|
+
- **Terminal Graphics**: Display profile pictures right in your terminal (via `imgcat`).
|
|
86
|
+
|
|
87
|
+
### ⚙️ Power Features
|
|
88
|
+
- **Auth Flexibility**: Hybrid support for `sp_dc` cookie, Desktop Client and OAuth.
|
|
89
|
+
- **CSV Logging**: Save all changes with full timestamps to a CSV file.
|
|
90
|
+
- **Flexible Config**: Support for files, dotenv and environment variables.
|
|
91
|
+
- **Signal Control**: Manage the running script via system signals (`SIGHUP`, `USR1`, etc.).
|
|
31
92
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
93
|
+
✨ If you want to track Spotify friends' music activity, check out another tool I developed: [spotify_monitor](https://github.com/misiektoja/spotify_monitor).
|
|
94
|
+
|
|
95
|
+
🛠️ If you're looking for debug tools to get Spotify Web Player access tokens and extract secret keys: [click here](#debugging-tools)
|
|
35
96
|
|
|
36
97
|
<a id="table-of-contents"></a>
|
|
37
98
|
## Table of Contents
|
|
@@ -156,10 +217,15 @@ Most settings can be configured via command-line arguments.
|
|
|
156
217
|
If you want to have it stored persistently, generate a default config template and save it to a file named `spotify_profile_monitor.conf`:
|
|
157
218
|
|
|
158
219
|
```sh
|
|
220
|
+
# On macOS, Linux or Windows Command Prompt (cmd.exe)
|
|
159
221
|
spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
160
222
|
|
|
223
|
+
# On Windows PowerShell (recommended to avoid encoding issues)
|
|
224
|
+
spotify_profile_monitor --generate-config spotify_profile_monitor.conf
|
|
161
225
|
```
|
|
162
226
|
|
|
227
|
+
> **IMPORTANT**: On **Windows PowerShell**, using redirection (`>`) can cause the file to be encoded in UTF-16, which will lead to "null bytes" errors when running the tool. It is highly recommended to provide the filename directly as an argument to `--generate-config` to ensure UTF-8 encoding.
|
|
228
|
+
|
|
163
229
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
164
230
|
|
|
165
231
|
**New in v3.1:** The tool now uses a hybrid authentication approach. OAuth app credentials (`SP_APP_CLIENT_ID`, `SP_APP_CLIENT_SECRET`) are required for playlist and user information retrieval when using either `cookie` or `client` token source methods. See [Spotify OAuth App](#spotify-oauth-app) section for setup instructions.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
|
|
4
|
-
v3.
|
|
4
|
+
v3.2
|
|
5
5
|
|
|
6
6
|
OSINT tool implementing real-time tracking of Spotify users activities and profile changes including playlists:
|
|
7
7
|
https://github.com/misiektoja/spotify_profile_monitor/
|
|
@@ -19,7 +19,7 @@ spotipy
|
|
|
19
19
|
wcwidth (optional, needed by TRUNCATE_CHARS feature)
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
VERSION = "3.
|
|
22
|
+
VERSION = "3.2"
|
|
23
23
|
|
|
24
24
|
# ---------------------------
|
|
25
25
|
# CONFIGURATION SECTION START
|
|
@@ -192,6 +192,12 @@ RECENTLY_PLAYED_ARTISTS_LIMIT_INFO = 15
|
|
|
192
192
|
# To avoid false alarms, we delay notifications until this happens PLAYLISTS_DISAPPEARED_COUNTER times in a row
|
|
193
193
|
PLAYLISTS_DISAPPEARED_COUNTER = 3
|
|
194
194
|
|
|
195
|
+
# Occasionally, the Spotify API glitches and returns incomplete/empty playlists list
|
|
196
|
+
# (e.g. network issues, API transient failures or playlists temporarily not visible)
|
|
197
|
+
# To avoid false alarms, we delay playlist change notifications until the same change is seen
|
|
198
|
+
# PLAYLISTS_CHANGE_COUNTER times in a row (set to 0 to disable this protection)
|
|
199
|
+
PLAYLISTS_CHANGE_COUNTER = 2
|
|
200
|
+
|
|
195
201
|
# Occasionally, the Spotify API glitches and returns an empty list of user followers / followings
|
|
196
202
|
# To avoid false alarms, we delay notifications until this happens FOLLOWERS_FOLLOWINGS_DISAPPEARED_COUNTER times in a row
|
|
197
203
|
FOLLOWERS_FOLLOWINGS_DISAPPEARED_COUNTER = 3
|
|
@@ -331,7 +337,6 @@ SECRET_CIPHER_DICT = {
|
|
|
331
337
|
# Remote or local URL used to fetch updated secrets needed for TOTP generation
|
|
332
338
|
# Set to empty string to disable
|
|
333
339
|
# If you used "spotify_monitor_secret_grabber.py --secretdict > secretDict.json" specify the file location below
|
|
334
|
-
# SECRET_CIPHER_DICT_URL = "https://github.com/Thereallo1026/spotify-secrets/blob/main/secrets/secretDict.json?raw=true"
|
|
335
340
|
SECRET_CIPHER_DICT_URL = "https://github.com/xyloflake/spot-secrets-go/blob/main/secrets/secretDict.json?raw=true"
|
|
336
341
|
# SECRET_CIPHER_DICT_URL = file:///C:/your_path/secretDict.json
|
|
337
342
|
# SECRET_CIPHER_DICT_URL = "file:///your_path/secretDict.json"
|
|
@@ -564,6 +569,7 @@ RECENTLY_PLAYED_ARTISTS_LIMIT_INFO = 0
|
|
|
564
569
|
PLAYLISTS_DISAPPEARED_COUNTER = 0
|
|
565
570
|
FOLLOWERS_FOLLOWINGS_DISAPPEARED_COUNTER = 0
|
|
566
571
|
COLLABORATORS_CHANGE_COUNTER = 0
|
|
572
|
+
PLAYLISTS_CHANGE_COUNTER = 0
|
|
567
573
|
USER_AGENT = ""
|
|
568
574
|
LIVENESS_CHECK_INTERVAL = 0
|
|
569
575
|
CHECK_INTERNET_URL = ""
|
|
@@ -648,6 +654,10 @@ GLITCH_CACHE = {}
|
|
|
648
654
|
COLLABORATORS_BASELINE_CACHE = {}
|
|
649
655
|
COLLABORATORS_PENDING_CACHE = {}
|
|
650
656
|
|
|
657
|
+
# Tracks transient playlists glitches to suppress false alerts
|
|
658
|
+
PLAYLISTS_BASELINE_CACHE = {}
|
|
659
|
+
PLAYLISTS_PENDING_CACHE = {}
|
|
660
|
+
|
|
651
661
|
LIVENESS_CHECK_COUNTER = LIVENESS_CHECK_INTERVAL / SPOTIFY_CHECK_INTERVAL
|
|
652
662
|
|
|
653
663
|
stdout_bck = None
|
|
@@ -774,7 +784,8 @@ class Logger(object):
|
|
|
774
784
|
self.logfile = open(filename, "a", buffering=1, encoding="utf-8")
|
|
775
785
|
|
|
776
786
|
def write(self, message):
|
|
777
|
-
|
|
787
|
+
# Expand tabs for file output (stdout remains untouched)
|
|
788
|
+
self.logfile.write(message.expandtabs(8))
|
|
778
789
|
if (TRUNCATE_CHARS):
|
|
779
790
|
message = truncate_string_per_line(message, TRUNCATE_CHARS)
|
|
780
791
|
self.terminal.write(message)
|
|
@@ -1691,6 +1702,8 @@ def fetch_and_update_secrets():
|
|
|
1691
1702
|
path = os.path.expanduser(os.path.expandvars(raw_path))
|
|
1692
1703
|
|
|
1693
1704
|
print(f"Loading Spotify web-player TOTP secrets from file: {path}")
|
|
1705
|
+
if os.path.getsize(path) == 0:
|
|
1706
|
+
raise ValueError(f"Secret file is empty: {path}")
|
|
1694
1707
|
with open(path, "r", encoding="utf-8") as f:
|
|
1695
1708
|
secrets = json.load(f)
|
|
1696
1709
|
print("─" * HORIZONTAL_LINE)
|
|
@@ -1698,21 +1711,26 @@ def fetch_and_update_secrets():
|
|
|
1698
1711
|
print(f"Fetching Spotify web-player TOTP secrets from URL: {SECRET_CIPHER_DICT_URL}")
|
|
1699
1712
|
response = req.get(SECRET_CIPHER_DICT_URL, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
1700
1713
|
response.raise_for_status()
|
|
1714
|
+
if not response.text.strip():
|
|
1715
|
+
raise ValueError("Fetched payload is empty")
|
|
1701
1716
|
secrets = response.json()
|
|
1702
1717
|
print("─" * HORIZONTAL_LINE)
|
|
1703
1718
|
|
|
1704
1719
|
if not isinstance(secrets, dict) or not secrets:
|
|
1705
|
-
raise ValueError("
|
|
1720
|
+
raise ValueError("Fetched payload not a non-empty dict")
|
|
1706
1721
|
|
|
1707
1722
|
for key, value in secrets.items():
|
|
1708
1723
|
if not isinstance(key, str) or not key.isdigit():
|
|
1709
|
-
raise ValueError(f"
|
|
1724
|
+
raise ValueError(f"Invalid key format: {key}")
|
|
1710
1725
|
if not isinstance(value, list) or not all(isinstance(x, int) for x in value):
|
|
1711
|
-
raise ValueError(f"
|
|
1726
|
+
raise ValueError(f"Invalid value format for key {key}")
|
|
1712
1727
|
|
|
1713
1728
|
SECRET_CIPHER_DICT = secrets
|
|
1714
1729
|
return True
|
|
1715
1730
|
|
|
1731
|
+
except json.JSONDecodeError as e:
|
|
1732
|
+
print(f"fetch_and_update_secrets(): Failed to parse secrets (invalid JSON format): {e}")
|
|
1733
|
+
return False
|
|
1716
1734
|
except Exception as e:
|
|
1717
1735
|
print(f"fetch_and_update_secrets(): Failed to get new secrets: {e}")
|
|
1718
1736
|
return False
|
|
@@ -5301,6 +5319,13 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
5301
5319
|
else:
|
|
5302
5320
|
# No change vs stable baseline; clear any pending candidate
|
|
5303
5321
|
if p_uri in COLLABORATORS_PENDING_CACHE:
|
|
5322
|
+
# If we had a pending change and we're back to stable baseline, this was a transient glitch that resolved - suppress notification
|
|
5323
|
+
suppress_collab_notification = True
|
|
5324
|
+
# Update the old values to match current stable baseline so the notification condition check fails
|
|
5325
|
+
p_collaborators_old = len(stable_ids)
|
|
5326
|
+
p_collaborators_list_old = stable_map
|
|
5327
|
+
p_collaborators = len(current_ids)
|
|
5328
|
+
p_collaborators_list = (p_collaborators_list or {}) if isinstance(p_collaborators_list, dict) else {}
|
|
5304
5329
|
try:
|
|
5305
5330
|
del COLLABORATORS_PENDING_CACHE[p_uri]
|
|
5306
5331
|
except Exception:
|
|
@@ -5622,11 +5647,97 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
5622
5647
|
if not error_while_processing:
|
|
5623
5648
|
list_of_playlists_old = list_of_playlists
|
|
5624
5649
|
|
|
5625
|
-
|
|
5650
|
+
# Suppress transient playlist glitches by confirming changes across multiple checks and keep a stable
|
|
5651
|
+
# baseline to avoid baseline poisoning
|
|
5652
|
+
global PLAYLISTS_BASELINE_CACHE
|
|
5653
|
+
global PLAYLISTS_PENDING_CACHE
|
|
5654
|
+
|
|
5655
|
+
# Helper to extract URI strings from playlist dict list for comparison
|
|
5656
|
+
def extract_playlist_uris(playlist_list):
|
|
5657
|
+
if not playlist_list:
|
|
5658
|
+
return frozenset()
|
|
5659
|
+
return frozenset(
|
|
5660
|
+
p.get("uri") if isinstance(p, dict) else p
|
|
5661
|
+
for p in playlist_list
|
|
5662
|
+
if p
|
|
5663
|
+
)
|
|
5664
|
+
|
|
5665
|
+
user_playlists_key = f"user:{user_uri_id}"
|
|
5666
|
+
stable_entry = PLAYLISTS_BASELINE_CACHE.get(user_playlists_key)
|
|
5667
|
+
if stable_entry is None:
|
|
5668
|
+
# Initialize baseline from previously persisted playlist snapshot (if available)
|
|
5669
|
+
stable_uris = extract_playlist_uris(playlists_old)
|
|
5670
|
+
stable_count = playlists_old_count
|
|
5671
|
+
# Store both the URI set (for comparison) and the full list (for restoration)
|
|
5672
|
+
PLAYLISTS_BASELINE_CACHE[user_playlists_key] = {"uris": stable_uris, "count": stable_count, "playlist_list": list(playlists_old) if playlists_old else []}
|
|
5673
|
+
stable_entry = PLAYLISTS_BASELINE_CACHE[user_playlists_key]
|
|
5674
|
+
|
|
5675
|
+
stable_uris = stable_entry.get("uris") or frozenset()
|
|
5676
|
+
stable_count = stable_entry.get("count", 0)
|
|
5677
|
+
stable_playlist_list = stable_entry.get("playlist_list") or []
|
|
5678
|
+
current_uris = extract_playlist_uris(playlists)
|
|
5679
|
+
suppress_playlists_notification = False
|
|
5680
|
+
|
|
5681
|
+
if current_uris != stable_uris:
|
|
5682
|
+
# Playlists have changed vs stable baseline
|
|
5683
|
+
# Skip PLAYLISTS_CHANGE_COUNTER protection when dropping to 0 - let PLAYLISTS_DISAPPEARED_COUNTER handle that case
|
|
5684
|
+
dropping_to_zero = len(current_uris) == 0
|
|
5685
|
+
|
|
5686
|
+
if not dropping_to_zero:
|
|
5687
|
+
pending = PLAYLISTS_PENDING_CACHE.get(user_playlists_key)
|
|
5688
|
+
if pending and pending.get("new_uris") == current_uris:
|
|
5689
|
+
pending["streak"] = int(pending.get("streak", 0)) + 1
|
|
5690
|
+
else:
|
|
5691
|
+
pending = {"new_uris": current_uris, "new_count": len(current_uris), "streak": 1, "first_seen_ts": time.time(), "playlist_list": list(playlists) if playlists else []}
|
|
5692
|
+
PLAYLISTS_PENDING_CACHE[user_playlists_key] = pending
|
|
5693
|
+
|
|
5694
|
+
if PLAYLISTS_CHANGE_COUNTER and int(pending.get("streak", 0)) < int(PLAYLISTS_CHANGE_COUNTER):
|
|
5695
|
+
print(f"* Spotify API: suspected transient playlist change for user '{username}' ({stable_count} -> {len(current_uris)}), streak {pending.get('streak')}/{PLAYLISTS_CHANGE_COUNTER}; will confirm next check\n")
|
|
5696
|
+
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
5697
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
5698
|
+
suppress_playlists_notification = True
|
|
5699
|
+
else:
|
|
5700
|
+
# Change confirmed - update variables for notification
|
|
5701
|
+
# Restore playlists_old from the stored dict list, not from URI strings
|
|
5702
|
+
playlists_old = stable_playlist_list
|
|
5703
|
+
playlists_old_count = stable_count
|
|
5704
|
+
playlists_count = len(current_uris)
|
|
5705
|
+
# playlists already contains current dict list, no change needed
|
|
5706
|
+
|
|
5707
|
+
# Update stable baseline and clear pending
|
|
5708
|
+
PLAYLISTS_BASELINE_CACHE[user_playlists_key] = {
|
|
5709
|
+
"uris": current_uris,
|
|
5710
|
+
"count": playlists_count,
|
|
5711
|
+
"playlist_list": list(playlists) if playlists else []
|
|
5712
|
+
}
|
|
5713
|
+
try:
|
|
5714
|
+
del PLAYLISTS_PENDING_CACHE[user_playlists_key]
|
|
5715
|
+
except Exception:
|
|
5716
|
+
pass
|
|
5717
|
+
# else: dropping to 0, let PLAYLISTS_DISAPPEARED_COUNTER handle it below
|
|
5718
|
+
else:
|
|
5719
|
+
# No change vs stable baseline; clear any pending candidate
|
|
5720
|
+
if user_playlists_key in PLAYLISTS_PENDING_CACHE:
|
|
5721
|
+
# If we had a pending change and we're back to stable baseline, this was a transient glitch that resolved - suppress notification
|
|
5722
|
+
suppress_playlists_notification = True
|
|
5723
|
+
# Update the old values to match current stable baseline so the notification condition check fails
|
|
5724
|
+
playlists_old_count = stable_count
|
|
5725
|
+
playlists_old = stable_playlist_list
|
|
5726
|
+
playlists_count = len(current_uris)
|
|
5727
|
+
# playlists already contains current dict list, no change needed
|
|
5728
|
+
print(f"* Spotify API: Playlists for user '{username}' reverted to baseline ({stable_count}) after transient glitch; suppressing notification\n")
|
|
5729
|
+
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
5730
|
+
print_cur_ts("Timestamp:\t\t\t")
|
|
5731
|
+
try:
|
|
5732
|
+
del PLAYLISTS_PENDING_CACHE[user_playlists_key]
|
|
5733
|
+
except Exception:
|
|
5734
|
+
pass
|
|
5735
|
+
|
|
5736
|
+
if not suppress_playlists_notification and playlists_count != playlists_old_count:
|
|
5626
5737
|
if playlists_count == 0:
|
|
5627
5738
|
playlists_zeroed_counter += 1
|
|
5628
5739
|
if playlists_zeroed_counter == PLAYLISTS_DISAPPEARED_COUNTER:
|
|
5629
|
-
print(f"* Spotify API: Playlists count dropped from {playlists_old_count} to 0 and has been 0 for {playlists_zeroed_counter} checks; accepting 0 as the new baseline")
|
|
5740
|
+
print(f"* Spotify API: Playlists count dropped from {playlists_old_count} to 0 and has been 0 for {playlists_zeroed_counter} checks; accepting 0 as the new baseline\n")
|
|
5630
5741
|
spotify_print_changed_followers_followings_playlists(
|
|
5631
5742
|
username, playlists, playlists_old, playlists_count, playlists_old_count, "Playlists", "for", "Added playlists to profile", "Added Playlist", "Removed playlists from profile", "Removed Playlist", playlists_file, csv_file_name, PROFILE_NOTIFICATION, True, sp_accessToken)
|
|
5632
5743
|
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
@@ -5634,13 +5745,15 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
5634
5745
|
playlists_old_count = playlists_count
|
|
5635
5746
|
playlists_old = playlists
|
|
5636
5747
|
playlists_zeroed_counter = 0
|
|
5748
|
+
# Update baseline after accepting 0 as new baseline
|
|
5749
|
+
PLAYLISTS_BASELINE_CACHE[user_playlists_key] = {"uris": frozenset(), "count": 0, "playlist_list": []}
|
|
5637
5750
|
elif playlists_zeroed_counter < PLAYLISTS_DISAPPEARED_COUNTER:
|
|
5638
|
-
print(f"* Spotify API: Playlists count dropped from {playlists_old_count} to 0, streak {playlists_zeroed_counter}/{PLAYLISTS_DISAPPEARED_COUNTER}; old count and list retained")
|
|
5751
|
+
print(f"* Spotify API: Playlists count dropped from {playlists_old_count} to 0, streak {playlists_zeroed_counter}/{PLAYLISTS_DISAPPEARED_COUNTER}; old count and list retained\n")
|
|
5639
5752
|
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
5640
5753
|
print_cur_ts("Timestamp:\t\t\t")
|
|
5641
5754
|
else:
|
|
5642
5755
|
if playlists_old_count == 0 and playlists_zeroed_counter >= PLAYLISTS_DISAPPEARED_COUNTER:
|
|
5643
|
-
print(f"* Spotify API: Playlists count recovered to {playlists_count}; previously was 0 for {playlists_zeroed_counter} checks (old baseline was {playlists_old_count})")
|
|
5756
|
+
print(f"* Spotify API: Playlists count recovered to {playlists_count}; previously was 0 for {playlists_zeroed_counter} checks (old baseline was {playlists_old_count})\n")
|
|
5644
5757
|
|
|
5645
5758
|
spotify_print_changed_followers_followings_playlists(username, playlists, playlists_old, playlists_count, playlists_old_count, "Playlists", "for", "Added playlists to profile", "Added Playlist", "Removed playlists from profile", "Removed Playlist", playlists_file, csv_file_name, PROFILE_NOTIFICATION, True, sp_accessToken)
|
|
5646
5759
|
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
@@ -5648,14 +5761,20 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
5648
5761
|
playlists_old_count = playlists_count
|
|
5649
5762
|
playlists_old = playlists
|
|
5650
5763
|
playlists_zeroed_counter = 0
|
|
5764
|
+
# Update baseline after confirmed change
|
|
5765
|
+
PLAYLISTS_BASELINE_CACHE[user_playlists_key] = {
|
|
5766
|
+
"uris": extract_playlist_uris(playlists),
|
|
5767
|
+
"count": playlists_count,
|
|
5768
|
+
"playlist_list": list(playlists) if playlists else []
|
|
5769
|
+
}
|
|
5651
5770
|
|
|
5652
|
-
elif playlists_count == playlists_old_count:
|
|
5771
|
+
elif not suppress_playlists_notification and playlists_count == playlists_old_count:
|
|
5653
5772
|
if playlists_count == 0:
|
|
5654
5773
|
playlists_zeroed_counter = 0
|
|
5655
5774
|
playlists_old = playlists
|
|
5656
5775
|
else:
|
|
5657
5776
|
if playlists_zeroed_counter > 0:
|
|
5658
|
-
print(f"* Spotify API: Playlists count recovered to {playlists_count} (matching old baseline) after a streak of {playlists_zeroed_counter} checks")
|
|
5777
|
+
print(f"* Spotify API: Playlists count recovered to {playlists_count} (matching old baseline) after a streak of {playlists_zeroed_counter} checks\n")
|
|
5659
5778
|
print(f"Check interval:\t\t\t{display_time(SPOTIFY_CHECK_INTERVAL)} ({get_range_of_dates_from_tss(int(time.time()) - SPOTIFY_CHECK_INTERVAL, int(time.time()), short=True)})")
|
|
5660
5779
|
print_cur_ts("Timestamp:\t\t\t")
|
|
5661
5780
|
playlists_zeroed_counter = 0
|
|
@@ -5674,7 +5793,22 @@ def main():
|
|
|
5674
5793
|
global CLI_CONFIG_PATH, DOTENV_FILE, LOCAL_TIMEZONE, LIVENESS_CHECK_COUNTER, SP_DC_COOKIE, SP_APP_CLIENT_ID, SP_APP_CLIENT_SECRET, SP_USER_CLIENT_ID, SP_USER_CLIENT_SECRET, LOGIN_REQUEST_BODY_FILE, CLIENTTOKEN_REQUEST_BODY_FILE, REFRESH_TOKEN, LOGIN_URL, USER_AGENT, DEVICE_ID, SYSTEM_ID, USER_URI_ID, CSV_FILE, PLAYLISTS_TO_SKIP_FILE, FILE_SUFFIX, DISABLE_LOGGING, SP_LOGFILE, PROFILE_NOTIFICATION, SPOTIFY_CHECK_INTERVAL, SPOTIFY_ERROR_INTERVAL, FOLLOWERS_FOLLOWINGS_NOTIFICATION, ERROR_NOTIFICATION, DETECT_CHANGED_PROFILE_PIC, DETECT_CHANGES_IN_PLAYLISTS, GET_ALL_PLAYLISTS, imgcat_exe, SMTP_PASSWORD, SP_SHA256, stdout_bck, APP_VERSION, CPU_ARCH, OS_BUILD, PLATFORM, OS_MAJOR, OS_MINOR, CLIENT_MODEL, TOKEN_SOURCE, ALARM_TIMEOUT, pyotp, CLEAN_OUTPUT, USER_AGENT, SP_APP_TOKENS_FILE, SP_USER_TOKENS_FILE, TRUNCATE_CHARS
|
|
5675
5794
|
|
|
5676
5795
|
if "--generate-config" in sys.argv:
|
|
5677
|
-
|
|
5796
|
+
config_content = CONFIG_BLOCK.strip("\n") + "\n"
|
|
5797
|
+
# Check if a filename was provided after --generate-config
|
|
5798
|
+
try:
|
|
5799
|
+
idx = sys.argv.index("--generate-config")
|
|
5800
|
+
if idx + 1 < len(sys.argv) and not sys.argv[idx + 1].startswith("-"):
|
|
5801
|
+
# Write directly to file (bypasses PowerShell UTF-16 encoding issue on Windows)
|
|
5802
|
+
output_file = sys.argv[idx + 1]
|
|
5803
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
5804
|
+
f.write(config_content)
|
|
5805
|
+
print(f"Config written to: {output_file}")
|
|
5806
|
+
sys.exit(0)
|
|
5807
|
+
except (ValueError, IndexError):
|
|
5808
|
+
pass
|
|
5809
|
+
# No filename provided - write to stdout using buffer to ensure UTF-8
|
|
5810
|
+
sys.stdout.buffer.write(config_content.encode("utf-8"))
|
|
5811
|
+
sys.stdout.buffer.flush()
|
|
5678
5812
|
sys.exit(0)
|
|
5679
5813
|
|
|
5680
5814
|
if "--version" in sys.argv:
|
|
@@ -5715,8 +5849,11 @@ def main():
|
|
|
5715
5849
|
)
|
|
5716
5850
|
conf.add_argument(
|
|
5717
5851
|
"--generate-config",
|
|
5718
|
-
|
|
5719
|
-
|
|
5852
|
+
dest="generate_config",
|
|
5853
|
+
nargs="?",
|
|
5854
|
+
const=True,
|
|
5855
|
+
metavar="FILENAME",
|
|
5856
|
+
help="Print default config template and exit (on Windows PowerShell, specify a filename to avoid redirect encoding issues)",
|
|
5720
5857
|
)
|
|
5721
5858
|
conf.add_argument(
|
|
5722
5859
|
"--env-file",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|