spotify-profile-monitor 2.7__tar.gz → 2.9__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-2.7 → spotify_profile_monitor-2.9}/PKG-INFO +135 -28
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/README.md +134 -27
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/pyproject.toml +1 -1
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/PKG-INFO +135 -28
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.py +286 -45
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/LICENSE +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/setup.cfg +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/SOURCES.txt +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/dependency_links.txt +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/entry_points.txt +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/requires.txt +0 -0
- {spotify_profile_monitor-2.7 → spotify_profile_monitor-2.9}/spotify_profile_monitor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -38,24 +38,25 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
38
38
|
<a id="features"></a>
|
|
39
39
|
## Features
|
|
40
40
|
|
|
41
|
-
- Real-time tracking of Spotify user activities and profile changes:
|
|
42
|
-
- addition/removal of followings and followers
|
|
43
|
-
- addition/removal of playlists
|
|
44
|
-
- addition/removal of tracks in playlists (including collaborator info for newly added tracks)
|
|
45
|
-
- playlists name and description changes
|
|
46
|
-
- number of likes for playlists
|
|
47
|
-
- number of collaborators for playlists
|
|
48
|
-
- profile picture changes
|
|
49
|
-
- username changes
|
|
50
|
-
- Email notifications for various events (as listed above)
|
|
51
|
-
- Attaching changed profile pictures directly to email notifications
|
|
52
|
-
- Displaying the profile picture right in your terminal (if you have `imgcat` installed)
|
|
53
|
-
- Additional functionalities on top of the monitoring mode allowing to display detailed info about the user
|
|
54
|
-
- Ability to display and export the list of tracks for a specific playlist (including Liked Songs for the user who owns the Spotify access token)
|
|
55
|
-
- Saving all profile changes (including playlists) with timestamps to the CSV file
|
|
56
|
-
- Clickable Spotify
|
|
57
|
-
- Support for four different methods to get a Spotify access token (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
58
|
-
- Possibility to control the running copy of the script via signals
|
|
41
|
+
- **Real-time tracking** of Spotify user activities and profile changes:
|
|
42
|
+
- addition/removal of **followings and followers**
|
|
43
|
+
- addition/removal of **playlists**
|
|
44
|
+
- addition/removal of **tracks in playlists** (including collaborator info for newly added tracks)
|
|
45
|
+
- **playlists name and description** changes
|
|
46
|
+
- **number of likes** for playlists
|
|
47
|
+
- **number of collaborators** for playlists
|
|
48
|
+
- **profile picture** changes
|
|
49
|
+
- **username** changes
|
|
50
|
+
- **Email notifications** for various events (as listed above)
|
|
51
|
+
- **Attaching changed profile pictures** directly to email notifications
|
|
52
|
+
- **Displaying the profile picture** right in your terminal (if you have `imgcat` installed)
|
|
53
|
+
- Additional functionalities on top of the monitoring mode allowing to display **detailed info about the user**, **list of followers & followings**, **recently played artists** and possibility to **search for users** in Spotify catalog with specific names
|
|
54
|
+
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
55
|
+
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
56
|
+
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
57
|
+
- Support for **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
58
|
+
- Possibility to **control the running copy** of the script via signals
|
|
59
|
+
- **Functional, procedural Python** (minimal OOP)
|
|
59
60
|
|
|
60
61
|
<p align="center">
|
|
61
62
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="100%"/>
|
|
@@ -68,6 +69,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
68
69
|
2. [Installation](#installation)
|
|
69
70
|
* [Install from PyPI](#install-from-pypi)
|
|
70
71
|
* [Manual Installation](#manual-installation)
|
|
72
|
+
* [Upgrading](#upgrading)
|
|
71
73
|
3. [Quick Start](#quick-start)
|
|
72
74
|
4. [Configuration](#configuration)
|
|
73
75
|
* [Configuration File](#configuration-file)
|
|
@@ -96,7 +98,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
96
98
|
* [Access Token Retrieval via sp_dc Cookie and TOTP](#access-token-retrieval-via-sp_dc-cookie-and-totp)
|
|
97
99
|
* [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles)
|
|
98
100
|
7. [Change Log](#change-log)
|
|
99
|
-
8. [
|
|
101
|
+
8. [Maintainers](#maintainers)
|
|
102
|
+
9. [License](#license)
|
|
100
103
|
|
|
101
104
|
<a id="requirements"></a>
|
|
102
105
|
## Requirements
|
|
@@ -106,8 +109,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
106
109
|
|
|
107
110
|
Tested on:
|
|
108
111
|
|
|
109
|
-
* **macOS**: Ventura, Sonoma, Sequoia
|
|
110
|
-
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm), Ubuntu 24, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
112
|
+
* **macOS**: Ventura, Sonoma, Sequoia, Tahoe
|
|
113
|
+
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm, Trixie), Ubuntu 24/25, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
111
114
|
* **Windows**: 10, 11
|
|
112
115
|
|
|
113
116
|
It should work on other versions of macOS, Linux, Unix and Windows as well.
|
|
@@ -139,6 +142,17 @@ Alternatively, from the downloaded *[requirements.txt](https://raw.githubusercon
|
|
|
139
142
|
pip install -r requirements.txt
|
|
140
143
|
```
|
|
141
144
|
|
|
145
|
+
<a id="upgrading"></a>
|
|
146
|
+
### Upgrading
|
|
147
|
+
|
|
148
|
+
To upgrade to the latest version when installed from PyPI:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
pip install spotify_profile_monitor -U
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If you installed manually, download the newest *[spotify_profile_monitor.py](https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/spotify_profile_monitor.py)* file to replace your existing installation.
|
|
155
|
+
|
|
142
156
|
<a id="quick-start"></a>
|
|
143
157
|
## Quick Start
|
|
144
158
|
|
|
@@ -178,6 +192,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
178
192
|
|
|
179
193
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
180
194
|
|
|
195
|
+
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
196
|
+
|
|
181
197
|
<a id="spotify-access-token-source"></a>
|
|
182
198
|
### Spotify access token source
|
|
183
199
|
|
|
@@ -309,6 +325,8 @@ This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
|
309
325
|
- Create a new app
|
|
310
326
|
|
|
311
327
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
328
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
329
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
312
330
|
|
|
313
331
|
- Select **Web API** as the intended API
|
|
314
332
|
|
|
@@ -343,6 +361,8 @@ This method uses an official Spotify Web API (Authorization Code OAuth flow).
|
|
|
343
361
|
- Create a new app
|
|
344
362
|
|
|
345
363
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
364
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
365
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
346
366
|
|
|
347
367
|
- Select **Web API** as the intended API
|
|
348
368
|
|
|
@@ -362,7 +382,7 @@ You can use the same client ID and secret values as those used for the [Spotify
|
|
|
362
382
|
Example:
|
|
363
383
|
|
|
364
384
|
```sh
|
|
365
|
-
spotify_profile_monitor --token-source oauth_user -
|
|
385
|
+
spotify_profile_monitor --token-source oauth_user -n "your_spotify_user_client_id:your_spotify_user_client_secret" <spotify_user_uri_id>
|
|
366
386
|
```
|
|
367
387
|
|
|
368
388
|
The tool takes care of refreshing the access token so it should remain valid indefinitely.
|
|
@@ -685,7 +705,7 @@ Make sure you defined your SMTP settings earlier (see [SMTP settings](#smtp-sett
|
|
|
685
705
|
Example email:
|
|
686
706
|
|
|
687
707
|
<p align="center">
|
|
688
|
-
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="
|
|
708
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="90%"/>
|
|
689
709
|
</p>
|
|
690
710
|
|
|
691
711
|
<a id="csv-export"></a>
|
|
@@ -853,13 +873,15 @@ You should get a valid Spotify access token, example output:
|
|
|
853
873
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
854
874
|
</p>
|
|
855
875
|
|
|
856
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in
|
|
876
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
857
877
|
|
|
858
878
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
859
879
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
860
880
|
|
|
861
881
|
The [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) tool automatically extracts secret keys used for TOTP generation in Spotify Web Player JavaScript bundles.
|
|
862
882
|
|
|
883
|
+
> 💡 **Quick tip:** The easiest and recommended way to run this tool is via Docker. Jump directly to the [Docker usage section below](#-secret-key-extraction-via-docker-recommended-easiest-way).
|
|
884
|
+
|
|
863
885
|
Download from [here](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) or:
|
|
864
886
|
|
|
865
887
|
```sh
|
|
@@ -873,7 +895,7 @@ pip install playwright
|
|
|
873
895
|
playwright install
|
|
874
896
|
```
|
|
875
897
|
|
|
876
|
-
Run:
|
|
898
|
+
Run interactively (default output mode):
|
|
877
899
|
|
|
878
900
|
```sh
|
|
879
901
|
python3 spotify_monitor_secret_grabber.py
|
|
@@ -885,15 +907,100 @@ You should get output similar to below:
|
|
|
885
907
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_secret_grabber.png" alt="spotify_monitor_secret_grabber" width="100%"/>
|
|
886
908
|
</p>
|
|
887
909
|
|
|
888
|
-
|
|
910
|
+
Show help:
|
|
911
|
+
```sh
|
|
912
|
+
python3 spotify_monitor_secret_grabber.py -h
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
<a id="cli-output-modes"></a>
|
|
918
|
+
### CLI Output Modes
|
|
919
|
+
|
|
920
|
+
The script supports several output modes for different use cases:
|
|
921
|
+
|
|
922
|
+
| Flag | Description | Output |
|
|
923
|
+
|------|--------------|--------|
|
|
924
|
+
| `--secret` | Prints plain JSON array of extracted secrets | `[{"version": X, "secret": "..."}, ...]` |
|
|
925
|
+
| `--secretbytes` | Prints JSON array with ASCII byte values | `[{"version": X, "secret": [..]}, ...]` |
|
|
926
|
+
| `--secretdict` | Prints JSON object/dict mapping version → byte list | `{"X": [..], "Y": [..]}` |
|
|
927
|
+
| `--all` | Extracts secrets and **writes all three outputs** to local files | `secrets.json`, `secretBytes.json`, `secretDict.json` |
|
|
928
|
+
|
|
929
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
930
|
+
|
|
931
|
+
```sh
|
|
932
|
+
python3 spotify_monitor_secret_grabber.py --secretdict > secretDict.json
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
936
|
+
|
|
937
|
+
```sh
|
|
938
|
+
python3 spotify_monitor_secret_grabber.py --all
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Default file paths and names can be configured directly in the `OUTPUT_FILES` dictionary at the top of the script.
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
<a id="-secret-key-extraction-via-docker-recommended-easiest-way"></a>
|
|
946
|
+
### 🐳 Secret Key Extraction via Docker (Recommended Easiest Way)
|
|
889
947
|
|
|
890
|
-
|
|
948
|
+
A prebuilt multi-architecture image is available on Docker Hub: [`misiektoja/spotify-secrets-grabber`](https://hub.docker.com/r/misiektoja/spotify-secrets-grabber)
|
|
949
|
+
|
|
950
|
+
This image works on:
|
|
951
|
+
- macOS (Intel & Apple Silicon)
|
|
952
|
+
- Linux (x86_64 and ARM64)
|
|
953
|
+
- Windows (Docker Desktop / WSL2)
|
|
954
|
+
- Raspberry Pi 4/5 (64-bit OS)
|
|
955
|
+
|
|
956
|
+
Run interactively (default output mode):
|
|
957
|
+
|
|
958
|
+
```sh
|
|
959
|
+
docker run --rm misiektoja/spotify-secrets-grabber
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Show help:
|
|
963
|
+
```sh
|
|
964
|
+
docker run --rm misiektoja/spotify-secrets-grabber -h
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
968
|
+
```sh
|
|
969
|
+
docker run --rm misiektoja/spotify-secrets-grabber --secretdict > secretDict.json
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
973
|
+
|
|
974
|
+
```sh
|
|
975
|
+
docker run --rm -v .:/work -w /work misiektoja/spotify-secrets-grabber --all
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
*For SELinux hosts (Fedora/RHEL), use `-v .:/work:Z`.*
|
|
979
|
+
|
|
980
|
+
<a id="optional-use-docker-compose-one-command-for-all-oss"></a>
|
|
981
|
+
Or optionally use Docker Compose (a preconfigured [compose.yaml](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber_docker/compose.yaml) file is included in the repo):
|
|
982
|
+
|
|
983
|
+
```sh
|
|
984
|
+
docker compose run --rm spotify-secrets-grabber --all
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
This will save all files into your current directory on any system (macOS, Linux or Windows).
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
You can now update the secrets used for TOTP generation (for example `SECRET_CIPHER_DICT` in `spotify_monitor_totp_test`, `spotify_monitor` and `spotify_profile_monitor`) either manually or by referencing an external `secretDict.json` file, which can be hosted in another repo or stored locally. See the description of `SECRET_CIPHER_DICT_URL` in those files for details.
|
|
891
992
|
|
|
892
993
|
<a id="change-log"></a>
|
|
893
994
|
## Change Log
|
|
894
995
|
|
|
895
996
|
See [RELEASE_NOTES.md](https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md) for details.
|
|
896
997
|
|
|
998
|
+
<a id="maintainers"></a>
|
|
999
|
+
## Maintainers
|
|
1000
|
+
|
|
1001
|
+
[](https://github.com/misiektoja)
|
|
1002
|
+
[](https://github.com/tomballgithub)
|
|
1003
|
+
|
|
897
1004
|
<a id="license"></a>
|
|
898
1005
|
## License
|
|
899
1006
|
|
|
@@ -9,24 +9,25 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
9
9
|
<a id="features"></a>
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- Real-time tracking of Spotify user activities and profile changes:
|
|
13
|
-
- addition/removal of followings and followers
|
|
14
|
-
- addition/removal of playlists
|
|
15
|
-
- addition/removal of tracks in playlists (including collaborator info for newly added tracks)
|
|
16
|
-
- playlists name and description changes
|
|
17
|
-
- number of likes for playlists
|
|
18
|
-
- number of collaborators for playlists
|
|
19
|
-
- profile picture changes
|
|
20
|
-
- username changes
|
|
21
|
-
- Email notifications for various events (as listed above)
|
|
22
|
-
- Attaching changed profile pictures directly to email notifications
|
|
23
|
-
- Displaying the profile picture right in your terminal (if you have `imgcat` installed)
|
|
24
|
-
- Additional functionalities on top of the monitoring mode allowing to display detailed info about the user
|
|
25
|
-
- Ability to display and export the list of tracks for a specific playlist (including Liked Songs for the user who owns the Spotify access token)
|
|
26
|
-
- Saving all profile changes (including playlists) with timestamps to the CSV file
|
|
27
|
-
- Clickable Spotify
|
|
28
|
-
- Support for four different methods to get a Spotify access token (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
29
|
-
- Possibility to control the running copy of the script via signals
|
|
12
|
+
- **Real-time tracking** of Spotify user activities and profile changes:
|
|
13
|
+
- addition/removal of **followings and followers**
|
|
14
|
+
- addition/removal of **playlists**
|
|
15
|
+
- addition/removal of **tracks in playlists** (including collaborator info for newly added tracks)
|
|
16
|
+
- **playlists name and description** changes
|
|
17
|
+
- **number of likes** for playlists
|
|
18
|
+
- **number of collaborators** for playlists
|
|
19
|
+
- **profile picture** changes
|
|
20
|
+
- **username** changes
|
|
21
|
+
- **Email notifications** for various events (as listed above)
|
|
22
|
+
- **Attaching changed profile pictures** directly to email notifications
|
|
23
|
+
- **Displaying the profile picture** right in your terminal (if you have `imgcat` installed)
|
|
24
|
+
- Additional functionalities on top of the monitoring mode allowing to display **detailed info about the user**, **list of followers & followings**, **recently played artists** and possibility to **search for users** in Spotify catalog with specific names
|
|
25
|
+
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
26
|
+
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
27
|
+
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
28
|
+
- Support for **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
29
|
+
- Possibility to **control the running copy** of the script via signals
|
|
30
|
+
- **Functional, procedural Python** (minimal OOP)
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
32
33
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="100%"/>
|
|
@@ -39,6 +40,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
39
40
|
2. [Installation](#installation)
|
|
40
41
|
* [Install from PyPI](#install-from-pypi)
|
|
41
42
|
* [Manual Installation](#manual-installation)
|
|
43
|
+
* [Upgrading](#upgrading)
|
|
42
44
|
3. [Quick Start](#quick-start)
|
|
43
45
|
4. [Configuration](#configuration)
|
|
44
46
|
* [Configuration File](#configuration-file)
|
|
@@ -67,7 +69,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
67
69
|
* [Access Token Retrieval via sp_dc Cookie and TOTP](#access-token-retrieval-via-sp_dc-cookie-and-totp)
|
|
68
70
|
* [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles)
|
|
69
71
|
7. [Change Log](#change-log)
|
|
70
|
-
8. [
|
|
72
|
+
8. [Maintainers](#maintainers)
|
|
73
|
+
9. [License](#license)
|
|
71
74
|
|
|
72
75
|
<a id="requirements"></a>
|
|
73
76
|
## Requirements
|
|
@@ -77,8 +80,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
77
80
|
|
|
78
81
|
Tested on:
|
|
79
82
|
|
|
80
|
-
* **macOS**: Ventura, Sonoma, Sequoia
|
|
81
|
-
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm), Ubuntu 24, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
83
|
+
* **macOS**: Ventura, Sonoma, Sequoia, Tahoe
|
|
84
|
+
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm, Trixie), Ubuntu 24/25, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
82
85
|
* **Windows**: 10, 11
|
|
83
86
|
|
|
84
87
|
It should work on other versions of macOS, Linux, Unix and Windows as well.
|
|
@@ -110,6 +113,17 @@ Alternatively, from the downloaded *[requirements.txt](https://raw.githubusercon
|
|
|
110
113
|
pip install -r requirements.txt
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
<a id="upgrading"></a>
|
|
117
|
+
### Upgrading
|
|
118
|
+
|
|
119
|
+
To upgrade to the latest version when installed from PyPI:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
pip install spotify_profile_monitor -U
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
If you installed manually, download the newest *[spotify_profile_monitor.py](https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/spotify_profile_monitor.py)* file to replace your existing installation.
|
|
126
|
+
|
|
113
127
|
<a id="quick-start"></a>
|
|
114
128
|
## Quick Start
|
|
115
129
|
|
|
@@ -149,6 +163,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
149
163
|
|
|
150
164
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
151
165
|
|
|
166
|
+
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
167
|
+
|
|
152
168
|
<a id="spotify-access-token-source"></a>
|
|
153
169
|
### Spotify access token source
|
|
154
170
|
|
|
@@ -280,6 +296,8 @@ This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
|
280
296
|
- Create a new app
|
|
281
297
|
|
|
282
298
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
299
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
300
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
283
301
|
|
|
284
302
|
- Select **Web API** as the intended API
|
|
285
303
|
|
|
@@ -314,6 +332,8 @@ This method uses an official Spotify Web API (Authorization Code OAuth flow).
|
|
|
314
332
|
- Create a new app
|
|
315
333
|
|
|
316
334
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
335
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
336
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
317
337
|
|
|
318
338
|
- Select **Web API** as the intended API
|
|
319
339
|
|
|
@@ -333,7 +353,7 @@ You can use the same client ID and secret values as those used for the [Spotify
|
|
|
333
353
|
Example:
|
|
334
354
|
|
|
335
355
|
```sh
|
|
336
|
-
spotify_profile_monitor --token-source oauth_user -
|
|
356
|
+
spotify_profile_monitor --token-source oauth_user -n "your_spotify_user_client_id:your_spotify_user_client_secret" <spotify_user_uri_id>
|
|
337
357
|
```
|
|
338
358
|
|
|
339
359
|
The tool takes care of refreshing the access token so it should remain valid indefinitely.
|
|
@@ -656,7 +676,7 @@ Make sure you defined your SMTP settings earlier (see [SMTP settings](#smtp-sett
|
|
|
656
676
|
Example email:
|
|
657
677
|
|
|
658
678
|
<p align="center">
|
|
659
|
-
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="
|
|
679
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="90%"/>
|
|
660
680
|
</p>
|
|
661
681
|
|
|
662
682
|
<a id="csv-export"></a>
|
|
@@ -824,13 +844,15 @@ You should get a valid Spotify access token, example output:
|
|
|
824
844
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
825
845
|
</p>
|
|
826
846
|
|
|
827
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in
|
|
847
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
828
848
|
|
|
829
849
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
830
850
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
831
851
|
|
|
832
852
|
The [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) tool automatically extracts secret keys used for TOTP generation in Spotify Web Player JavaScript bundles.
|
|
833
853
|
|
|
854
|
+
> 💡 **Quick tip:** The easiest and recommended way to run this tool is via Docker. Jump directly to the [Docker usage section below](#-secret-key-extraction-via-docker-recommended-easiest-way).
|
|
855
|
+
|
|
834
856
|
Download from [here](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) or:
|
|
835
857
|
|
|
836
858
|
```sh
|
|
@@ -844,7 +866,7 @@ pip install playwright
|
|
|
844
866
|
playwright install
|
|
845
867
|
```
|
|
846
868
|
|
|
847
|
-
Run:
|
|
869
|
+
Run interactively (default output mode):
|
|
848
870
|
|
|
849
871
|
```sh
|
|
850
872
|
python3 spotify_monitor_secret_grabber.py
|
|
@@ -856,15 +878,100 @@ You should get output similar to below:
|
|
|
856
878
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_secret_grabber.png" alt="spotify_monitor_secret_grabber" width="100%"/>
|
|
857
879
|
</p>
|
|
858
880
|
|
|
859
|
-
|
|
881
|
+
Show help:
|
|
882
|
+
```sh
|
|
883
|
+
python3 spotify_monitor_secret_grabber.py -h
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
---
|
|
887
|
+
|
|
888
|
+
<a id="cli-output-modes"></a>
|
|
889
|
+
### CLI Output Modes
|
|
890
|
+
|
|
891
|
+
The script supports several output modes for different use cases:
|
|
892
|
+
|
|
893
|
+
| Flag | Description | Output |
|
|
894
|
+
|------|--------------|--------|
|
|
895
|
+
| `--secret` | Prints plain JSON array of extracted secrets | `[{"version": X, "secret": "..."}, ...]` |
|
|
896
|
+
| `--secretbytes` | Prints JSON array with ASCII byte values | `[{"version": X, "secret": [..]}, ...]` |
|
|
897
|
+
| `--secretdict` | Prints JSON object/dict mapping version → byte list | `{"X": [..], "Y": [..]}` |
|
|
898
|
+
| `--all` | Extracts secrets and **writes all three outputs** to local files | `secrets.json`, `secretBytes.json`, `secretDict.json` |
|
|
899
|
+
|
|
900
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
901
|
+
|
|
902
|
+
```sh
|
|
903
|
+
python3 spotify_monitor_secret_grabber.py --secretdict > secretDict.json
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
907
|
+
|
|
908
|
+
```sh
|
|
909
|
+
python3 spotify_monitor_secret_grabber.py --all
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
Default file paths and names can be configured directly in the `OUTPUT_FILES` dictionary at the top of the script.
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
<a id="-secret-key-extraction-via-docker-recommended-easiest-way"></a>
|
|
917
|
+
### 🐳 Secret Key Extraction via Docker (Recommended Easiest Way)
|
|
860
918
|
|
|
861
|
-
|
|
919
|
+
A prebuilt multi-architecture image is available on Docker Hub: [`misiektoja/spotify-secrets-grabber`](https://hub.docker.com/r/misiektoja/spotify-secrets-grabber)
|
|
920
|
+
|
|
921
|
+
This image works on:
|
|
922
|
+
- macOS (Intel & Apple Silicon)
|
|
923
|
+
- Linux (x86_64 and ARM64)
|
|
924
|
+
- Windows (Docker Desktop / WSL2)
|
|
925
|
+
- Raspberry Pi 4/5 (64-bit OS)
|
|
926
|
+
|
|
927
|
+
Run interactively (default output mode):
|
|
928
|
+
|
|
929
|
+
```sh
|
|
930
|
+
docker run --rm misiektoja/spotify-secrets-grabber
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
Show help:
|
|
934
|
+
```sh
|
|
935
|
+
docker run --rm misiektoja/spotify-secrets-grabber -h
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
939
|
+
```sh
|
|
940
|
+
docker run --rm misiektoja/spotify-secrets-grabber --secretdict > secretDict.json
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
944
|
+
|
|
945
|
+
```sh
|
|
946
|
+
docker run --rm -v .:/work -w /work misiektoja/spotify-secrets-grabber --all
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
*For SELinux hosts (Fedora/RHEL), use `-v .:/work:Z`.*
|
|
950
|
+
|
|
951
|
+
<a id="optional-use-docker-compose-one-command-for-all-oss"></a>
|
|
952
|
+
Or optionally use Docker Compose (a preconfigured [compose.yaml](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber_docker/compose.yaml) file is included in the repo):
|
|
953
|
+
|
|
954
|
+
```sh
|
|
955
|
+
docker compose run --rm spotify-secrets-grabber --all
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
This will save all files into your current directory on any system (macOS, Linux or Windows).
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
You can now update the secrets used for TOTP generation (for example `SECRET_CIPHER_DICT` in `spotify_monitor_totp_test`, `spotify_monitor` and `spotify_profile_monitor`) either manually or by referencing an external `secretDict.json` file, which can be hosted in another repo or stored locally. See the description of `SECRET_CIPHER_DICT_URL` in those files for details.
|
|
862
963
|
|
|
863
964
|
<a id="change-log"></a>
|
|
864
965
|
## Change Log
|
|
865
966
|
|
|
866
967
|
See [RELEASE_NOTES.md](https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md) for details.
|
|
867
968
|
|
|
969
|
+
<a id="maintainers"></a>
|
|
970
|
+
## Maintainers
|
|
971
|
+
|
|
972
|
+
[](https://github.com/misiektoja)
|
|
973
|
+
[](https://github.com/tomballgithub)
|
|
974
|
+
|
|
868
975
|
<a id="license"></a>
|
|
869
976
|
## License
|
|
870
977
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spotify_profile_monitor"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.9"
|
|
8
8
|
description = "Tool implementing real-time tracking of Spotify users activities and profile changes including playlists"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spotify_profile_monitor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9
|
|
4
4
|
Summary: Tool implementing real-time tracking of Spotify users activities and profile changes including playlists
|
|
5
5
|
Author-email: Michal Szymanski <misiektoja-pypi@rm-rf.ninja>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -38,24 +38,25 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
38
38
|
<a id="features"></a>
|
|
39
39
|
## Features
|
|
40
40
|
|
|
41
|
-
- Real-time tracking of Spotify user activities and profile changes:
|
|
42
|
-
- addition/removal of followings and followers
|
|
43
|
-
- addition/removal of playlists
|
|
44
|
-
- addition/removal of tracks in playlists (including collaborator info for newly added tracks)
|
|
45
|
-
- playlists name and description changes
|
|
46
|
-
- number of likes for playlists
|
|
47
|
-
- number of collaborators for playlists
|
|
48
|
-
- profile picture changes
|
|
49
|
-
- username changes
|
|
50
|
-
- Email notifications for various events (as listed above)
|
|
51
|
-
- Attaching changed profile pictures directly to email notifications
|
|
52
|
-
- Displaying the profile picture right in your terminal (if you have `imgcat` installed)
|
|
53
|
-
- Additional functionalities on top of the monitoring mode allowing to display detailed info about the user
|
|
54
|
-
- Ability to display and export the list of tracks for a specific playlist (including Liked Songs for the user who owns the Spotify access token)
|
|
55
|
-
- Saving all profile changes (including playlists) with timestamps to the CSV file
|
|
56
|
-
- Clickable Spotify
|
|
57
|
-
- Support for four different methods to get a Spotify access token (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
58
|
-
- Possibility to control the running copy of the script via signals
|
|
41
|
+
- **Real-time tracking** of Spotify user activities and profile changes:
|
|
42
|
+
- addition/removal of **followings and followers**
|
|
43
|
+
- addition/removal of **playlists**
|
|
44
|
+
- addition/removal of **tracks in playlists** (including collaborator info for newly added tracks)
|
|
45
|
+
- **playlists name and description** changes
|
|
46
|
+
- **number of likes** for playlists
|
|
47
|
+
- **number of collaborators** for playlists
|
|
48
|
+
- **profile picture** changes
|
|
49
|
+
- **username** changes
|
|
50
|
+
- **Email notifications** for various events (as listed above)
|
|
51
|
+
- **Attaching changed profile pictures** directly to email notifications
|
|
52
|
+
- **Displaying the profile picture** right in your terminal (if you have `imgcat` installed)
|
|
53
|
+
- Additional functionalities on top of the monitoring mode allowing to display **detailed info about the user**, **list of followers & followings**, **recently played artists** and possibility to **search for users** in Spotify catalog with specific names
|
|
54
|
+
- Ability to **display and export** the list of tracks for a specific playlist (including **Liked Songs** for the user who owns the Spotify access token)
|
|
55
|
+
- **Saving all profile changes** (including playlists) with timestamps to the **CSV file**
|
|
56
|
+
- **Clickable** **Spotify**, **Apple Music**, **YouTube Music**, **Amazon Music**, **Deezer**, **Tidal**, **Genius Lyrics**, **AZLyrics**, **Tekstowo.pl**, **Musixmatch** and **Lyrics.com** search URLs printed in the console and included in email notifications (configurable per service)
|
|
57
|
+
- Support for **four different methods to get a Spotify access token** (`sp_dc cookie`, `desktop client`, `OAuth app`, `OAuth user`)
|
|
58
|
+
- Possibility to **control the running copy** of the script via signals
|
|
59
|
+
- **Functional, procedural Python** (minimal OOP)
|
|
59
60
|
|
|
60
61
|
<p align="center">
|
|
61
62
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor.png" alt="spotify_profile_monitor_screenshot" width="100%"/>
|
|
@@ -68,6 +69,7 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
68
69
|
2. [Installation](#installation)
|
|
69
70
|
* [Install from PyPI](#install-from-pypi)
|
|
70
71
|
* [Manual Installation](#manual-installation)
|
|
72
|
+
* [Upgrading](#upgrading)
|
|
71
73
|
3. [Quick Start](#quick-start)
|
|
72
74
|
4. [Configuration](#configuration)
|
|
73
75
|
* [Configuration File](#configuration-file)
|
|
@@ -96,7 +98,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
96
98
|
* [Access Token Retrieval via sp_dc Cookie and TOTP](#access-token-retrieval-via-sp_dc-cookie-and-totp)
|
|
97
99
|
* [Secret Key Extraction from Spotify Web Player Bundles](#secret-key-extraction-from-spotify-web-player-bundles)
|
|
98
100
|
7. [Change Log](#change-log)
|
|
99
|
-
8. [
|
|
101
|
+
8. [Maintainers](#maintainers)
|
|
102
|
+
9. [License](#license)
|
|
100
103
|
|
|
101
104
|
<a id="requirements"></a>
|
|
102
105
|
## Requirements
|
|
@@ -106,8 +109,8 @@ OSINT tool for real-time monitoring of **Spotify users' activities and profile c
|
|
|
106
109
|
|
|
107
110
|
Tested on:
|
|
108
111
|
|
|
109
|
-
* **macOS**: Ventura, Sonoma, Sequoia
|
|
110
|
-
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm), Ubuntu 24, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
112
|
+
* **macOS**: Ventura, Sonoma, Sequoia, Tahoe
|
|
113
|
+
* **Linux**: Raspberry Pi OS (Bullseye, Bookworm, Trixie), Ubuntu 24/25, Rocky Linux 8.x/9.x, Kali Linux 2024/2025
|
|
111
114
|
* **Windows**: 10, 11
|
|
112
115
|
|
|
113
116
|
It should work on other versions of macOS, Linux, Unix and Windows as well.
|
|
@@ -139,6 +142,17 @@ Alternatively, from the downloaded *[requirements.txt](https://raw.githubusercon
|
|
|
139
142
|
pip install -r requirements.txt
|
|
140
143
|
```
|
|
141
144
|
|
|
145
|
+
<a id="upgrading"></a>
|
|
146
|
+
### Upgrading
|
|
147
|
+
|
|
148
|
+
To upgrade to the latest version when installed from PyPI:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
pip install spotify_profile_monitor -U
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If you installed manually, download the newest *[spotify_profile_monitor.py](https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/spotify_profile_monitor.py)* file to replace your existing installation.
|
|
155
|
+
|
|
142
156
|
<a id="quick-start"></a>
|
|
143
157
|
## Quick Start
|
|
144
158
|
|
|
@@ -178,6 +192,8 @@ spotify_profile_monitor --generate-config > spotify_profile_monitor.conf
|
|
|
178
192
|
|
|
179
193
|
Edit the `spotify_profile_monitor.conf` file and change any desired configuration options (detailed comments are provided for each).
|
|
180
194
|
|
|
195
|
+
**New in v2.9:** The configuration file includes options to enable/disable music service URLs (Apple Music, YouTube Music, Amazon Music, Deezer, Tidal) and lyrics service URLs (Genius, AZLyrics, Tekstowo.pl, Musixmatch, Lyrics.com) in console and email outputs.
|
|
196
|
+
|
|
181
197
|
<a id="spotify-access-token-source"></a>
|
|
182
198
|
### Spotify access token source
|
|
183
199
|
|
|
@@ -309,6 +325,8 @@ This method uses an official Spotify Web API (Client Credentials OAuth flow).
|
|
|
309
325
|
- Create a new app
|
|
310
326
|
|
|
311
327
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
328
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
329
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
312
330
|
|
|
313
331
|
- Select **Web API** as the intended API
|
|
314
332
|
|
|
@@ -343,6 +361,8 @@ This method uses an official Spotify Web API (Authorization Code OAuth flow).
|
|
|
343
361
|
- Create a new app
|
|
344
362
|
|
|
345
363
|
- For **Redirect URL**, use: http://127.0.0.1:1234
|
|
364
|
+
- The URL must match exactly as shown, including not having a / at the end
|
|
365
|
+
- When copying the link via right-click, some browsers may add an extra / to the URL
|
|
346
366
|
|
|
347
367
|
- Select **Web API** as the intended API
|
|
348
368
|
|
|
@@ -362,7 +382,7 @@ You can use the same client ID and secret values as those used for the [Spotify
|
|
|
362
382
|
Example:
|
|
363
383
|
|
|
364
384
|
```sh
|
|
365
|
-
spotify_profile_monitor --token-source oauth_user -
|
|
385
|
+
spotify_profile_monitor --token-source oauth_user -n "your_spotify_user_client_id:your_spotify_user_client_secret" <spotify_user_uri_id>
|
|
366
386
|
```
|
|
367
387
|
|
|
368
388
|
The tool takes care of refreshing the access token so it should remain valid indefinitely.
|
|
@@ -685,7 +705,7 @@ Make sure you defined your SMTP settings earlier (see [SMTP settings](#smtp-sett
|
|
|
685
705
|
Example email:
|
|
686
706
|
|
|
687
707
|
<p align="center">
|
|
688
|
-
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="
|
|
708
|
+
<img src="https://raw.githubusercontent.com/misiektoja/spotify_profile_monitor/refs/heads/main/assets/spotify_profile_monitor_email_notifications.png" alt="spotify_profile_monitor_email_notifications" width="90%"/>
|
|
689
709
|
</p>
|
|
690
710
|
|
|
691
711
|
<a id="csv-export"></a>
|
|
@@ -853,13 +873,15 @@ You should get a valid Spotify access token, example output:
|
|
|
853
873
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_totp_test.png" alt="spotify_monitor_totp_test" width="100%"/>
|
|
854
874
|
</p>
|
|
855
875
|
|
|
856
|
-
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in
|
|
876
|
+
> **NOTE:** secrets used for TOTP generation (`SECRET_CIPHER_DICT`) expire every two days; you can either run the [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) and extract it by yourself (see [here](#secret-key-extraction-from-spotify-web-player-bundles) for more info) or you can pass `--fetch-secrets` flag in `spotify_monitor_totp_test` (available since v1.6). There is also a [xyloflake/spot-secrets-go/](https://github.com/xyloflake/spot-secrets-go/) repo which offers JSON files that are automatically updated with current secrets (you can pass `--download-secrets` flag in `spotify_monitor_totp_test` to get it automatically from remote URL, available since v1.8).
|
|
857
877
|
|
|
858
878
|
<a id="secret-key-extraction-from-spotify-web-player-bundles"></a>
|
|
859
879
|
### Secret Key Extraction from Spotify Web Player Bundles
|
|
860
880
|
|
|
861
881
|
The [spotify_monitor_secret_grabber](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) tool automatically extracts secret keys used for TOTP generation in Spotify Web Player JavaScript bundles.
|
|
862
882
|
|
|
883
|
+
> 💡 **Quick tip:** The easiest and recommended way to run this tool is via Docker. Jump directly to the [Docker usage section below](#-secret-key-extraction-via-docker-recommended-easiest-way).
|
|
884
|
+
|
|
863
885
|
Download from [here](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber.py) or:
|
|
864
886
|
|
|
865
887
|
```sh
|
|
@@ -873,7 +895,7 @@ pip install playwright
|
|
|
873
895
|
playwright install
|
|
874
896
|
```
|
|
875
897
|
|
|
876
|
-
Run:
|
|
898
|
+
Run interactively (default output mode):
|
|
877
899
|
|
|
878
900
|
```sh
|
|
879
901
|
python3 spotify_monitor_secret_grabber.py
|
|
@@ -885,15 +907,100 @@ You should get output similar to below:
|
|
|
885
907
|
<img src="https://raw.githubusercontent.com/misiektoja/spotify_monitor/refs/heads/main/assets/spotify_monitor_secret_grabber.png" alt="spotify_monitor_secret_grabber" width="100%"/>
|
|
886
908
|
</p>
|
|
887
909
|
|
|
888
|
-
|
|
910
|
+
Show help:
|
|
911
|
+
```sh
|
|
912
|
+
python3 spotify_monitor_secret_grabber.py -h
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
<a id="cli-output-modes"></a>
|
|
918
|
+
### CLI Output Modes
|
|
919
|
+
|
|
920
|
+
The script supports several output modes for different use cases:
|
|
921
|
+
|
|
922
|
+
| Flag | Description | Output |
|
|
923
|
+
|------|--------------|--------|
|
|
924
|
+
| `--secret` | Prints plain JSON array of extracted secrets | `[{"version": X, "secret": "..."}, ...]` |
|
|
925
|
+
| `--secretbytes` | Prints JSON array with ASCII byte values | `[{"version": X, "secret": [..]}, ...]` |
|
|
926
|
+
| `--secretdict` | Prints JSON object/dict mapping version → byte list | `{"X": [..], "Y": [..]}` |
|
|
927
|
+
| `--all` | Extracts secrets and **writes all three outputs** to local files | `secrets.json`, `secretBytes.json`, `secretDict.json` |
|
|
928
|
+
|
|
929
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
930
|
+
|
|
931
|
+
```sh
|
|
932
|
+
python3 spotify_monitor_secret_grabber.py --secretdict > secretDict.json
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
936
|
+
|
|
937
|
+
```sh
|
|
938
|
+
python3 spotify_monitor_secret_grabber.py --all
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Default file paths and names can be configured directly in the `OUTPUT_FILES` dictionary at the top of the script.
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
<a id="-secret-key-extraction-via-docker-recommended-easiest-way"></a>
|
|
946
|
+
### 🐳 Secret Key Extraction via Docker (Recommended Easiest Way)
|
|
889
947
|
|
|
890
|
-
|
|
948
|
+
A prebuilt multi-architecture image is available on Docker Hub: [`misiektoja/spotify-secrets-grabber`](https://hub.docker.com/r/misiektoja/spotify-secrets-grabber)
|
|
949
|
+
|
|
950
|
+
This image works on:
|
|
951
|
+
- macOS (Intel & Apple Silicon)
|
|
952
|
+
- Linux (x86_64 and ARM64)
|
|
953
|
+
- Windows (Docker Desktop / WSL2)
|
|
954
|
+
- Raspberry Pi 4/5 (64-bit OS)
|
|
955
|
+
|
|
956
|
+
Run interactively (default output mode):
|
|
957
|
+
|
|
958
|
+
```sh
|
|
959
|
+
docker run --rm misiektoja/spotify-secrets-grabber
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Show help:
|
|
963
|
+
```sh
|
|
964
|
+
docker run --rm misiektoja/spotify-secrets-grabber -h
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
Print extracted secrets in specific format, for example Python-friendly secret bytes (JSON object/dict) and save to indicated file:
|
|
968
|
+
```sh
|
|
969
|
+
docker run --rm misiektoja/spotify-secrets-grabber --secretdict > secretDict.json
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
Or, to generate and save all secret formats to files (`secrets.json`, `secretBytes.json`, `secretDict.json`) at once:
|
|
973
|
+
|
|
974
|
+
```sh
|
|
975
|
+
docker run --rm -v .:/work -w /work misiektoja/spotify-secrets-grabber --all
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
*For SELinux hosts (Fedora/RHEL), use `-v .:/work:Z`.*
|
|
979
|
+
|
|
980
|
+
<a id="optional-use-docker-compose-one-command-for-all-oss"></a>
|
|
981
|
+
Or optionally use Docker Compose (a preconfigured [compose.yaml](https://github.com/misiektoja/spotify_monitor/blob/dev/debug/spotify_monitor_secret_grabber_docker/compose.yaml) file is included in the repo):
|
|
982
|
+
|
|
983
|
+
```sh
|
|
984
|
+
docker compose run --rm spotify-secrets-grabber --all
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
This will save all files into your current directory on any system (macOS, Linux or Windows).
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
You can now update the secrets used for TOTP generation (for example `SECRET_CIPHER_DICT` in `spotify_monitor_totp_test`, `spotify_monitor` and `spotify_profile_monitor`) either manually or by referencing an external `secretDict.json` file, which can be hosted in another repo or stored locally. See the description of `SECRET_CIPHER_DICT_URL` in those files for details.
|
|
891
992
|
|
|
892
993
|
<a id="change-log"></a>
|
|
893
994
|
## Change Log
|
|
894
995
|
|
|
895
996
|
See [RELEASE_NOTES.md](https://github.com/misiektoja/spotify_profile_monitor/blob/main/RELEASE_NOTES.md) for details.
|
|
896
997
|
|
|
998
|
+
<a id="maintainers"></a>
|
|
999
|
+
## Maintainers
|
|
1000
|
+
|
|
1001
|
+
[](https://github.com/misiektoja)
|
|
1002
|
+
[](https://github.com/tomballgithub)
|
|
1003
|
+
|
|
897
1004
|
<a id="license"></a>
|
|
898
1005
|
## License
|
|
899
1006
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Author: Michal Szymanski <misiektoja-github@rm-rf.ninja>
|
|
4
|
-
v2.
|
|
4
|
+
v2.9
|
|
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 (optional, needed when the token source is set to oauth_app)
|
|
|
19
19
|
wcwidth (optional, needed by TRUNCATE_CHARS feature)
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
VERSION = "2.
|
|
22
|
+
VERSION = "2.9"
|
|
23
23
|
|
|
24
24
|
# ---------------------------
|
|
25
25
|
# CONFIGURATION SECTION START
|
|
@@ -247,6 +247,38 @@ TRUNCATE_CHARS = 0
|
|
|
247
247
|
# Value used by signal handlers to increase or decrease profile check interval (SPOTIFY_CHECK_INTERVAL); in seconds
|
|
248
248
|
SPOTIFY_CHECK_SIGNAL_VALUE = 300 # 5 minutes
|
|
249
249
|
|
|
250
|
+
# Whether to show Apple Music URL in console and emails
|
|
251
|
+
ENABLE_APPLE_MUSIC_URL = True
|
|
252
|
+
|
|
253
|
+
# Whether to show YouTube Music URL in console and emails
|
|
254
|
+
ENABLE_YOUTUBE_MUSIC_URL = True
|
|
255
|
+
|
|
256
|
+
# Whether to show Amazon Music URL in console and emails
|
|
257
|
+
ENABLE_AMAZON_MUSIC_URL = False
|
|
258
|
+
|
|
259
|
+
# Whether to show Deezer URL in console and emails
|
|
260
|
+
ENABLE_DEEZER_URL = False
|
|
261
|
+
|
|
262
|
+
# Whether to show Tidal URL in console and emails
|
|
263
|
+
# Note: Tidal requires users to be logged in to their account in the web browser to use the search functionality
|
|
264
|
+
ENABLE_TIDAL_URL = False
|
|
265
|
+
|
|
266
|
+
# Whether to show Genius lyrics URL in console and emails
|
|
267
|
+
ENABLE_GENIUS_LYRICS_URL = True
|
|
268
|
+
|
|
269
|
+
# Whether to show AZLyrics URL in console and emails
|
|
270
|
+
ENABLE_AZLYRICS_URL = False
|
|
271
|
+
|
|
272
|
+
# Whether to show Tekstowo.pl lyrics URL in console and emails
|
|
273
|
+
ENABLE_TEKSTOWO_URL = False
|
|
274
|
+
|
|
275
|
+
# Whether to show Musixmatch lyrics URL in console and emails
|
|
276
|
+
# Note: Musixmatch requires users to be logged in to their account in the web browser to use the search functionality
|
|
277
|
+
ENABLE_MUSIXMATCH_URL = False
|
|
278
|
+
|
|
279
|
+
# Whether to show Lyrics.com lyrics URL in console and emails
|
|
280
|
+
ENABLE_LYRICS_COM_URL = False
|
|
281
|
+
|
|
250
282
|
# ---------------------------------------------------------------------
|
|
251
283
|
|
|
252
284
|
# The section below is used when the token source is set to 'cookie'
|
|
@@ -261,19 +293,17 @@ TOKEN_RETRY_TIMEOUT = 0.5 # 0.5 second
|
|
|
261
293
|
# Newest secrets are downloaded automatically from SECRET_CIPHER_DICT_URL (see below)
|
|
262
294
|
# Can also be fetched via spotify_monitor_secret_grabber.py utility - see debug dir
|
|
263
295
|
SECRET_CIPHER_DICT = {
|
|
264
|
-
"12": [107, 81, 49, 57, 67, 93, 87, 81, 69, 67, 40, 93, 48, 50, 46, 91, 94, 113, 41, 108, 77, 107, 34],
|
|
265
|
-
"11": [111, 45, 40, 73, 95, 74, 35, 85, 105, 107, 60, 110, 55, 72, 69, 70, 114, 83, 63, 88, 91],
|
|
266
|
-
"10": [61, 110, 58, 98, 35, 79, 117, 69, 102, 72, 92, 102, 69, 93, 41, 101, 42, 75],
|
|
267
|
-
"9": [109, 101, 90, 99, 66, 92, 116, 108, 85, 70, 86, 49, 68, 54, 87, 50, 72, 121, 52, 64, 57, 43, 36, 81, 97, 72, 53, 41, 78, 56],
|
|
268
|
-
"8": [37, 84, 32, 76, 87, 90, 87, 47, 13, 75, 48, 54, 44, 28, 19, 21, 22],
|
|
269
|
-
"7": [59, 91, 66, 74, 30, 66, 74, 38, 46, 50, 72, 61, 44, 71, 86, 39, 89],
|
|
270
296
|
"6": [21, 24, 85, 46, 48, 35, 33, 8, 11, 63, 76, 12, 55, 77, 14, 7, 54],
|
|
271
297
|
"5": [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54],
|
|
272
298
|
}
|
|
273
299
|
|
|
274
|
-
# Remote URL used to fetch updated secrets needed for TOTP generation
|
|
300
|
+
# Remote or local URL used to fetch updated secrets needed for TOTP generation
|
|
275
301
|
# Set to empty string to disable
|
|
276
|
-
|
|
302
|
+
# If you used "spotify_monitor_secret_grabber.py --secretdict > secretDict.json" specify the file location below
|
|
303
|
+
# SECRET_CIPHER_DICT_URL = "https://github.com/Thereallo1026/spotify-secrets/blob/main/secrets/secretDict.json?raw=true"
|
|
304
|
+
SECRET_CIPHER_DICT_URL = "https://github.com/xyloflake/spot-secrets-go/blob/main/secrets/secretDict.json?raw=true"
|
|
305
|
+
# SECRET_CIPHER_DICT_URL = file:///C:/your_path/secretDict.json
|
|
306
|
+
# SECRET_CIPHER_DICT_URL = "file:///your_path/secretDict.json"
|
|
277
307
|
|
|
278
308
|
# Identifier used to select the appropriate secret from SECRET_CIPHER_DICT when generating a TOTP token
|
|
279
309
|
# Set to 0 to auto-select the highest available version
|
|
@@ -544,6 +574,16 @@ DISABLE_LOGGING = False
|
|
|
544
574
|
HORIZONTAL_LINE = 0
|
|
545
575
|
CLEAR_SCREEN = False
|
|
546
576
|
SPOTIFY_CHECK_SIGNAL_VALUE = 0
|
|
577
|
+
ENABLE_APPLE_MUSIC_URL = False
|
|
578
|
+
ENABLE_YOUTUBE_MUSIC_URL = False
|
|
579
|
+
ENABLE_AMAZON_MUSIC_URL = False
|
|
580
|
+
ENABLE_DEEZER_URL = False
|
|
581
|
+
ENABLE_TIDAL_URL = False
|
|
582
|
+
ENABLE_GENIUS_LYRICS_URL = False
|
|
583
|
+
ENABLE_AZLYRICS_URL = False
|
|
584
|
+
ENABLE_TEKSTOWO_URL = False
|
|
585
|
+
ENABLE_MUSIXMATCH_URL = False
|
|
586
|
+
ENABLE_LYRICS_COM_URL = False
|
|
547
587
|
TOKEN_MAX_RETRIES = 0
|
|
548
588
|
TOKEN_RETRY_TIMEOUT = 0.0
|
|
549
589
|
SECRET_CIPHER_DICT = {}
|
|
@@ -1294,17 +1334,126 @@ def reload_secrets_signal_handler(sig, frame):
|
|
|
1294
1334
|
print_cur_ts("Timestamp:\t\t\t")
|
|
1295
1335
|
|
|
1296
1336
|
|
|
1297
|
-
# Returns Apple &
|
|
1337
|
+
# Returns Apple & lyrics search URLs for specified track
|
|
1298
1338
|
def get_apple_genius_search_urls(artist, track):
|
|
1299
|
-
|
|
1300
|
-
youtube_music_search_string = quote_plus(
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1339
|
+
spotify_search_string = f"{artist} {track}"
|
|
1340
|
+
youtube_music_search_string = quote_plus(spotify_search_string)
|
|
1341
|
+
# Clean search string for lyrics services (remove remaster, extended, etc.)
|
|
1342
|
+
lyrics_search_string = spotify_search_string
|
|
1343
|
+
if re.search(re_search_str, lyrics_search_string, re.IGNORECASE):
|
|
1344
|
+
lyrics_search_string = re.sub(re_replace_str, '', lyrics_search_string, flags=re.IGNORECASE)
|
|
1345
|
+
apple_search_string = quote(spotify_search_string)
|
|
1304
1346
|
apple_search_url = f"https://music.apple.com/pl/search?term={apple_search_string}"
|
|
1305
|
-
genius_search_url = f"https://genius.com/search?q={quote_plus(
|
|
1347
|
+
genius_search_url = f"https://genius.com/search?q={quote_plus(lyrics_search_string)}"
|
|
1348
|
+
azlyrics_search_url = f"https://www.azlyrics.com/search/?q={quote_plus(lyrics_search_string)}"
|
|
1349
|
+
tekstowo_search_url = f"https://www.tekstowo.pl/szukaj,{quote_plus(lyrics_search_string)}.html"
|
|
1350
|
+
musixmatch_search_url = f"https://www.musixmatch.com/search?query={quote_plus(lyrics_search_string)}"
|
|
1351
|
+
lyrics_com_search_url = f"https://www.lyrics.com/serp.php?st={quote_plus(lyrics_search_string)}&qtype=1"
|
|
1306
1352
|
youtube_music_search_url = f"https://music.youtube.com/search?q={youtube_music_search_string}"
|
|
1307
|
-
|
|
1353
|
+
amazon_music_search_url = f"https://music.amazon.com/search/{quote_plus(spotify_search_string)}"
|
|
1354
|
+
deezer_search_url = f"https://www.deezer.com/search/{quote_plus(spotify_search_string)}"
|
|
1355
|
+
tidal_search_url = f"https://tidal.com/search?q={quote_plus(spotify_search_string)}"
|
|
1356
|
+
return apple_search_url, genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
# Formats lyrics URLs for console output based on configuration
|
|
1360
|
+
def format_lyrics_urls_console(genius_url, azlyrics_url, tekstowo_url, musixmatch_url, lyrics_com_url):
|
|
1361
|
+
lines = []
|
|
1362
|
+
if ENABLE_GENIUS_LYRICS_URL:
|
|
1363
|
+
lines.append(f"Genius lyrics URL: {genius_url}")
|
|
1364
|
+
if ENABLE_AZLYRICS_URL:
|
|
1365
|
+
lines.append(f"AZLyrics URL: {azlyrics_url}")
|
|
1366
|
+
if ENABLE_TEKSTOWO_URL:
|
|
1367
|
+
lines.append(f"Tekstowo.pl URL: {tekstowo_url}")
|
|
1368
|
+
if ENABLE_MUSIXMATCH_URL:
|
|
1369
|
+
lines.append(f"Musixmatch URL: {musixmatch_url}")
|
|
1370
|
+
if ENABLE_LYRICS_COM_URL:
|
|
1371
|
+
lines.append(f"Lyrics.com URL: {lyrics_com_url}")
|
|
1372
|
+
return "\n".join(lines) if lines else ""
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
# Formats lyrics URLs for plain text email body based on configuration
|
|
1376
|
+
def format_lyrics_urls_email_text(genius_url, azlyrics_url, tekstowo_url, musixmatch_url, lyrics_com_url):
|
|
1377
|
+
lines = []
|
|
1378
|
+
if ENABLE_GENIUS_LYRICS_URL:
|
|
1379
|
+
lines.append(f"Genius lyrics URL: {genius_url}")
|
|
1380
|
+
if ENABLE_AZLYRICS_URL:
|
|
1381
|
+
lines.append(f"AZLyrics URL: {azlyrics_url}")
|
|
1382
|
+
if ENABLE_TEKSTOWO_URL:
|
|
1383
|
+
lines.append(f"Tekstowo.pl URL: {tekstowo_url}")
|
|
1384
|
+
if ENABLE_MUSIXMATCH_URL:
|
|
1385
|
+
lines.append(f"Musixmatch URL: {musixmatch_url}")
|
|
1386
|
+
if ENABLE_LYRICS_COM_URL:
|
|
1387
|
+
lines.append(f"Lyrics.com URL: {lyrics_com_url}")
|
|
1388
|
+
return "\n".join(lines) if lines else ""
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
# Formats lyrics URLs for HTML email body based on configuration
|
|
1392
|
+
def format_lyrics_urls_email_html(genius_url, azlyrics_url, tekstowo_url, musixmatch_url, lyrics_com_url, artist, track):
|
|
1393
|
+
lines = []
|
|
1394
|
+
escaped_artist = escape(artist)
|
|
1395
|
+
escaped_track = escape(track)
|
|
1396
|
+
if ENABLE_GENIUS_LYRICS_URL:
|
|
1397
|
+
lines.append(f'Genius lyrics URL: <a href="{genius_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1398
|
+
if ENABLE_AZLYRICS_URL:
|
|
1399
|
+
lines.append(f'AZLyrics URL: <a href="{azlyrics_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1400
|
+
if ENABLE_TEKSTOWO_URL:
|
|
1401
|
+
lines.append(f'Tekstowo.pl URL: <a href="{tekstowo_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1402
|
+
if ENABLE_MUSIXMATCH_URL:
|
|
1403
|
+
lines.append(f'Musixmatch URL: <a href="{musixmatch_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1404
|
+
if ENABLE_LYRICS_COM_URL:
|
|
1405
|
+
lines.append(f'Lyrics.com URL: <a href="{lyrics_com_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1406
|
+
return "<br>".join(lines) if lines else ""
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
# Formats music service URLs for console output based on configuration
|
|
1410
|
+
def format_music_urls_console(apple_music_url, youtube_music_url, amazon_music_url, deezer_url, tidal_url):
|
|
1411
|
+
lines = []
|
|
1412
|
+
if ENABLE_APPLE_MUSIC_URL:
|
|
1413
|
+
lines.append(f"Apple Music URL: {apple_music_url}")
|
|
1414
|
+
if ENABLE_YOUTUBE_MUSIC_URL:
|
|
1415
|
+
lines.append(f"YouTube Music URL: {youtube_music_url}")
|
|
1416
|
+
if ENABLE_AMAZON_MUSIC_URL:
|
|
1417
|
+
lines.append(f"Amazon Music URL: {amazon_music_url}")
|
|
1418
|
+
if ENABLE_DEEZER_URL:
|
|
1419
|
+
lines.append(f"Deezer URL: {deezer_url}")
|
|
1420
|
+
if ENABLE_TIDAL_URL:
|
|
1421
|
+
lines.append(f"Tidal URL: {tidal_url}")
|
|
1422
|
+
return "\n".join(lines) if lines else ""
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
# Formats music service URLs for plain text email body based on configuration
|
|
1426
|
+
def format_music_urls_email_text(apple_music_url, youtube_music_url, amazon_music_url, deezer_url, tidal_url):
|
|
1427
|
+
lines = []
|
|
1428
|
+
if ENABLE_APPLE_MUSIC_URL:
|
|
1429
|
+
lines.append(f"Apple Music URL: {apple_music_url}")
|
|
1430
|
+
if ENABLE_YOUTUBE_MUSIC_URL:
|
|
1431
|
+
lines.append(f"YouTube Music URL: {youtube_music_url}")
|
|
1432
|
+
if ENABLE_AMAZON_MUSIC_URL:
|
|
1433
|
+
lines.append(f"Amazon Music URL: {amazon_music_url}")
|
|
1434
|
+
if ENABLE_DEEZER_URL:
|
|
1435
|
+
lines.append(f"Deezer URL: {deezer_url}")
|
|
1436
|
+
if ENABLE_TIDAL_URL:
|
|
1437
|
+
lines.append(f"Tidal URL: {tidal_url}")
|
|
1438
|
+
return "\n".join(lines) if lines else ""
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
# Formats music service URLs for HTML email body based on configuration
|
|
1442
|
+
def format_music_urls_email_html(apple_music_url, youtube_music_url, amazon_music_url, deezer_url, tidal_url, artist, track):
|
|
1443
|
+
lines = []
|
|
1444
|
+
escaped_artist = escape(artist)
|
|
1445
|
+
escaped_track = escape(track)
|
|
1446
|
+
if ENABLE_APPLE_MUSIC_URL:
|
|
1447
|
+
lines.append(f'Apple Music URL: <a href="{apple_music_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1448
|
+
if ENABLE_YOUTUBE_MUSIC_URL:
|
|
1449
|
+
lines.append(f'YouTube Music URL: <a href="{youtube_music_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1450
|
+
if ENABLE_AMAZON_MUSIC_URL:
|
|
1451
|
+
lines.append(f'Amazon Music URL: <a href="{amazon_music_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1452
|
+
if ENABLE_DEEZER_URL:
|
|
1453
|
+
lines.append(f'Deezer URL: <a href="{deezer_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1454
|
+
if ENABLE_TIDAL_URL:
|
|
1455
|
+
lines.append(f'Tidal URL: <a href="{tidal_url}">{escaped_artist} - {escaped_track}</a>')
|
|
1456
|
+
return "<br>".join(lines) if lines else ""
|
|
1308
1457
|
|
|
1309
1458
|
|
|
1310
1459
|
# Extracts Spotify ID from URI or URL and return cleaned name
|
|
@@ -1498,12 +1647,43 @@ def fetch_and_update_secrets():
|
|
|
1498
1647
|
return False
|
|
1499
1648
|
|
|
1500
1649
|
try:
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1650
|
+
if SECRET_CIPHER_DICT_URL.startswith("file:"):
|
|
1651
|
+
import os
|
|
1652
|
+
from urllib.parse import urlparse, unquote
|
|
1653
|
+
|
|
1654
|
+
parsed = urlparse(SECRET_CIPHER_DICT_URL)
|
|
1655
|
+
|
|
1656
|
+
if parsed.netloc:
|
|
1657
|
+
raw_path = f"/{parsed.netloc}{parsed.path or ''}"
|
|
1658
|
+
else:
|
|
1659
|
+
if SECRET_CIPHER_DICT_URL.startswith("file://"):
|
|
1660
|
+
raw_path = parsed.path or SECRET_CIPHER_DICT_URL[len("file://"):]
|
|
1661
|
+
else:
|
|
1662
|
+
raw_path = parsed.path or SECRET_CIPHER_DICT_URL[len("file:"):]
|
|
1663
|
+
|
|
1664
|
+
raw_path = unquote(raw_path)
|
|
1665
|
+
|
|
1666
|
+
if raw_path.startswith("/~"):
|
|
1667
|
+
raw_path = raw_path[1:]
|
|
1668
|
+
|
|
1669
|
+
if not raw_path.startswith("/") and not raw_path.startswith("~"):
|
|
1670
|
+
raw_path = "/" + raw_path
|
|
1671
|
+
|
|
1672
|
+
path = os.path.expanduser(os.path.expandvars(raw_path))
|
|
1673
|
+
|
|
1674
|
+
print(f"Loading Spotify web-player TOTP secrets from file: {path}")
|
|
1675
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
1676
|
+
secrets = json.load(f)
|
|
1677
|
+
print("─" * HORIZONTAL_LINE)
|
|
1678
|
+
else:
|
|
1679
|
+
print(f"Fetching Spotify web-player TOTP secrets from URL: {SECRET_CIPHER_DICT_URL}")
|
|
1680
|
+
response = req.get(SECRET_CIPHER_DICT_URL, timeout=FUNCTION_TIMEOUT, verify=VERIFY_SSL)
|
|
1681
|
+
response.raise_for_status()
|
|
1682
|
+
secrets = response.json()
|
|
1683
|
+
print("─" * HORIZONTAL_LINE)
|
|
1504
1684
|
|
|
1505
1685
|
if not isinstance(secrets, dict) or not secrets:
|
|
1506
|
-
raise ValueError("fetch_and_update_secrets(): Fetched payload not a non
|
|
1686
|
+
raise ValueError("fetch_and_update_secrets(): Fetched payload not a non-empty dict")
|
|
1507
1687
|
|
|
1508
1688
|
for key, value in secrets.items():
|
|
1509
1689
|
if not isinstance(key, str) or not key.isdigit():
|
|
@@ -2520,7 +2700,12 @@ def is_token_owner(access_token, user_uri_id) -> bool:
|
|
|
2520
2700
|
|
|
2521
2701
|
# Returns detailed info about playlist with specified URI (with possibility to get its tracks as well)
|
|
2522
2702
|
def spotify_get_playlist_info(access_token, playlist_uri, get_tracks):
|
|
2523
|
-
|
|
2703
|
+
parts = playlist_uri.split(':')
|
|
2704
|
+
if len(parts) == 3:
|
|
2705
|
+
playlist_id = parts[2]
|
|
2706
|
+
else:
|
|
2707
|
+
playlist_id = "invalid_playlist"
|
|
2708
|
+
print(f"Invalid playlist format")
|
|
2524
2709
|
|
|
2525
2710
|
if get_tracks:
|
|
2526
2711
|
url1 = f"https://api.spotify.com/v1/playlists/{playlist_id}?fields=name,description,owner,followers,external_urls,tracks.total,collaborative,images"
|
|
@@ -2911,7 +3096,11 @@ def spotify_list_tracks_for_playlist(sp_accessToken, playlist_url, csv_file_name
|
|
|
2911
3096
|
user_id_name_mapping = {}
|
|
2912
3097
|
user_track_counts = Counter()
|
|
2913
3098
|
|
|
2914
|
-
|
|
3099
|
+
pattern = re.compile(r'^[a-zA-Z0-9]{22}$')
|
|
3100
|
+
if (pattern.match(playlist_url)):
|
|
3101
|
+
playlist_uri = f"::{playlist_url}"
|
|
3102
|
+
else:
|
|
3103
|
+
playlist_uri = spotify_convert_url_to_uri(playlist_url)
|
|
2915
3104
|
|
|
2916
3105
|
sp_playlist_data = spotify_get_playlist_info(sp_accessToken, playlist_uri, True)
|
|
2917
3106
|
|
|
@@ -3893,7 +4082,8 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
3893
4082
|
|
|
3894
4083
|
out = f"Monitoring user {user_uri_id}"
|
|
3895
4084
|
print(out)
|
|
3896
|
-
print("-" * len(out))
|
|
4085
|
+
# print("-" * len(out))
|
|
4086
|
+
print("─" * HORIZONTAL_LINE)
|
|
3897
4087
|
|
|
3898
4088
|
try:
|
|
3899
4089
|
if TOKEN_SOURCE == "client":
|
|
@@ -3939,9 +4129,10 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
3939
4129
|
|
|
3940
4130
|
if user_info:
|
|
3941
4131
|
if TOKEN_SOURCE == "oauth_app":
|
|
3942
|
-
print(f"Token belongs to:\t\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})
|
|
4132
|
+
print(f"Token belongs to:\t\t{user_info.get('client_id', '')} (via {TOKEN_SOURCE})")
|
|
3943
4133
|
else:
|
|
3944
|
-
print(f"Token belongs to:\t\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t\t[ {user_info.get('spotify_url')} ]
|
|
4134
|
+
print(f"Token belongs to:\t\t{user_info.get('display_name', '')} (via {TOKEN_SOURCE})\n\t\t\t\t[ {user_info.get('spotify_url')} ]")
|
|
4135
|
+
print("─" * HORIZONTAL_LINE)
|
|
3945
4136
|
|
|
3946
4137
|
username = sp_user_data["sp_username"]
|
|
3947
4138
|
image_url = sp_user_data["sp_user_image_url"]
|
|
@@ -4754,12 +4945,35 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4754
4945
|
|
|
4755
4946
|
for f_dict in added_tracks:
|
|
4756
4947
|
if "artist" in f_dict and "track" in f_dict:
|
|
4757
|
-
apple_search_url, genius_search_url, youtube_music_search_url = get_apple_genius_search_urls(f_dict["artist"], f_dict["track"])
|
|
4948
|
+
apple_search_url, genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url = get_apple_genius_search_urls(f_dict["artist"], f_dict["track"])
|
|
4758
4949
|
tempuri = f'spotify:user:{f_dict["added_by_id"]}'
|
|
4759
|
-
|
|
4760
|
-
|
|
4950
|
+
music_urls_output = format_music_urls_console(apple_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url)
|
|
4951
|
+
lyrics_urls_output = format_lyrics_urls_console(genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url)
|
|
4952
|
+
music_urls_text = format_music_urls_email_text(apple_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url)
|
|
4953
|
+
lyrics_urls_text = format_lyrics_urls_email_text(genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url)
|
|
4954
|
+
added_track_console = f'- {f_dict["artist"]} - {f_dict["track"]} [ {get_date_from_ts(f_dict["added_at"])}, {f_dict["added_by"]} ]\n[ Spotify URL: {spotify_convert_uri_to_url(f_dict["uri"])} ]\n'
|
|
4955
|
+
if music_urls_output:
|
|
4956
|
+
for line in music_urls_output.split("\n"):
|
|
4957
|
+
if line:
|
|
4958
|
+
added_track_console += f"[ {line} ]\n"
|
|
4959
|
+
if lyrics_urls_output:
|
|
4960
|
+
for line in lyrics_urls_output.split("\n"):
|
|
4961
|
+
if line:
|
|
4962
|
+
added_track_console += f"[ {line} ]\n"
|
|
4963
|
+
added_track_console += f'[ Collaborator URL: {spotify_convert_uri_to_url(tempuri)} ]\n\n'
|
|
4964
|
+
added_track_email = f'- {f_dict["artist"]} - {f_dict["track"]} [ {get_date_from_ts(f_dict["added_at"])}, {f_dict["added_by"]} ]\n[ Spotify URL: {spotify_convert_uri_to_url(f_dict["uri"])} ]\n'
|
|
4965
|
+
if music_urls_text:
|
|
4966
|
+
for line in music_urls_text.split("\n"):
|
|
4967
|
+
if line:
|
|
4968
|
+
added_track_email += f"[ {line} ]\n"
|
|
4969
|
+
if lyrics_urls_text:
|
|
4970
|
+
for line in lyrics_urls_text.split("\n"):
|
|
4971
|
+
if line:
|
|
4972
|
+
added_track_email += f"[ {line} ]\n"
|
|
4973
|
+
added_track_email += f'[ Collaborator URL: {spotify_convert_uri_to_url(tempuri)} ]\n\n'
|
|
4974
|
+
p_message_added_tracks += added_track_email
|
|
4761
4975
|
added_at_dt = f_dict['added_at']
|
|
4762
|
-
print(
|
|
4976
|
+
print(added_track_console, end="")
|
|
4763
4977
|
try:
|
|
4764
4978
|
if csv_file_name:
|
|
4765
4979
|
write_csv_entry(csv_file_name, convert_to_local_naive(added_at_dt), "Added Track", p_name, f_dict['added_by'], f_dict["artist"] + " - " + f_dict["track"])
|
|
@@ -4772,11 +4986,34 @@ def spotify_profile_monitor_uri(user_uri_id, csv_file_name, playlists_to_skip):
|
|
|
4772
4986
|
|
|
4773
4987
|
for f_dict in removed_tracks:
|
|
4774
4988
|
if "artist" in f_dict and "track" in f_dict:
|
|
4775
|
-
apple_search_url, genius_search_url, youtube_music_search_url = get_apple_genius_search_urls(f_dict["artist"], f_dict["track"])
|
|
4989
|
+
apple_search_url, genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url = get_apple_genius_search_urls(f_dict["artist"], f_dict["track"])
|
|
4776
4990
|
tempuri = f'spotify:user:{f_dict["added_by_id"]}'
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4991
|
+
music_urls_output = format_music_urls_console(apple_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url)
|
|
4992
|
+
lyrics_urls_output = format_lyrics_urls_console(genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url)
|
|
4993
|
+
music_urls_text = format_music_urls_email_text(apple_search_url, youtube_music_search_url, amazon_music_search_url, deezer_search_url, tidal_search_url)
|
|
4994
|
+
lyrics_urls_text = format_lyrics_urls_email_text(genius_search_url, azlyrics_search_url, tekstowo_search_url, musixmatch_search_url, lyrics_com_search_url)
|
|
4995
|
+
removed_track_console = f'- {f_dict["artist"]} - {f_dict["track"]} [ {get_date_from_ts(f_dict["added_at"])}, {f_dict["added_by"]} ]\n[ Spotify URL: {spotify_convert_uri_to_url(f_dict["uri"])} ]\n'
|
|
4996
|
+
if music_urls_output:
|
|
4997
|
+
for line in music_urls_output.split("\n"):
|
|
4998
|
+
if line:
|
|
4999
|
+
removed_track_console += f"[ {line} ]\n"
|
|
5000
|
+
if lyrics_urls_output:
|
|
5001
|
+
for line in lyrics_urls_output.split("\n"):
|
|
5002
|
+
if line:
|
|
5003
|
+
removed_track_console += f"[ {line} ]\n"
|
|
5004
|
+
removed_track_console += f'[ Collaborator URL: {spotify_convert_uri_to_url(tempuri)} ]\n\n'
|
|
5005
|
+
removed_track_email = f'- {f_dict["artist"]} - {f_dict["track"]} [ {get_date_from_ts(f_dict["added_at"])}, {f_dict["added_by"]} ]\n[ Spotify URL: {spotify_convert_uri_to_url(f_dict["uri"])} ]\n'
|
|
5006
|
+
if music_urls_text:
|
|
5007
|
+
for line in music_urls_text.split("\n"):
|
|
5008
|
+
if line:
|
|
5009
|
+
removed_track_email += f"[ {line} ]\n"
|
|
5010
|
+
if lyrics_urls_text:
|
|
5011
|
+
for line in lyrics_urls_text.split("\n"):
|
|
5012
|
+
if line:
|
|
5013
|
+
removed_track_email += f"[ {line} ]\n"
|
|
5014
|
+
removed_track_email += f'[ Collaborator URL: {spotify_convert_uri_to_url(tempuri)} ]\n\n'
|
|
5015
|
+
p_message_removed_tracks += removed_track_email
|
|
5016
|
+
print(removed_track_console, end="")
|
|
4780
5017
|
try:
|
|
4781
5018
|
if csv_file_name:
|
|
4782
5019
|
write_csv_entry(csv_file_name, now_local_naive(), "Removed Track", p_name, f_dict["artist"] + " - " + f_dict["track"], "")
|
|
@@ -5165,16 +5402,6 @@ def main():
|
|
|
5165
5402
|
|
|
5166
5403
|
args = parser.parse_args()
|
|
5167
5404
|
|
|
5168
|
-
if args.export_for_spotify_monitor:
|
|
5169
|
-
CLEAN_OUTPUT = True
|
|
5170
|
-
|
|
5171
|
-
if not CLEAN_OUTPUT:
|
|
5172
|
-
stdout_bck = sys.stdout
|
|
5173
|
-
|
|
5174
|
-
clear_screen(CLEAR_SCREEN)
|
|
5175
|
-
|
|
5176
|
-
print(f"Spotify Profile Monitoring Tool v{VERSION}\n")
|
|
5177
|
-
|
|
5178
5405
|
if len(sys.argv) == 1:
|
|
5179
5406
|
parser.print_help(sys.stderr)
|
|
5180
5407
|
sys.exit(1)
|
|
@@ -5229,6 +5456,20 @@ def main():
|
|
|
5229
5456
|
if val is not None:
|
|
5230
5457
|
globals()[secret] = val
|
|
5231
5458
|
|
|
5459
|
+
if args.export_for_spotify_monitor:
|
|
5460
|
+
if not args.list_tracks_for_playlist and not args.list_liked_tracks:
|
|
5461
|
+
print(f"* Error: The 'export for spotify monitor' feature is only supported with -l and -x command line options !")
|
|
5462
|
+
sys.exit(2)
|
|
5463
|
+
else:
|
|
5464
|
+
CLEAN_OUTPUT = True
|
|
5465
|
+
|
|
5466
|
+
if not CLEAN_OUTPUT:
|
|
5467
|
+
stdout_bck = sys.stdout
|
|
5468
|
+
|
|
5469
|
+
clear_screen(CLEAR_SCREEN)
|
|
5470
|
+
|
|
5471
|
+
print(f"Spotify Profile Monitoring Tool v{VERSION}\n")
|
|
5472
|
+
|
|
5232
5473
|
local_tz = None
|
|
5233
5474
|
if LOCAL_TIMEZONE == "Auto":
|
|
5234
5475
|
if get_localzone is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|