@akilles/soundcloud-watcher 2.0.4 → 2.1.0
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.
- package/README.md +72 -123
- package/index.ts +27 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/soundcloud_watcher.ts +92 -27
package/README.md
CHANGED
|
@@ -2,214 +2,163 @@
|
|
|
2
2
|
|
|
3
3
|
Monitor your SoundCloud account and track artist releases. Get notified when someone follows you, likes your tracks, or when artists you care about drop new music.
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
## Features
|
|
7
6
|
|
|
8
7
|
- **Follower tracking** - See who followed you recently
|
|
9
8
|
- **Track engagement** - Monitor who liked your tracks
|
|
10
9
|
- **New releases** - Get notifications when tracked artists release new music
|
|
11
|
-
- **Smart API usage** - Only fetches what changed, automatically skips dormant artists
|
|
10
|
+
- **Smart API usage** - Only fetches what changed, automatically skips dormant artists
|
|
12
11
|
- **Rate limit handling** - Exponential backoff for API reliability
|
|
13
|
-
- **Automatic background checking** - Configurable interval (default: 6 hours)
|
|
14
|
-
- **Session-agnostic notifications** - Works with any OpenClaw session (Telegram, Discord, etc.)
|
|
15
12
|
|
|
16
13
|
## Prerequisites
|
|
17
14
|
|
|
18
15
|
- OpenClaw gateway running
|
|
19
16
|
- Node.js 22+ installed
|
|
20
|
-
- SoundCloud API credentials
|
|
17
|
+
- SoundCloud API credentials
|
|
21
18
|
|
|
22
19
|
## Quick Start
|
|
23
20
|
|
|
24
21
|
### 1. Install
|
|
25
22
|
|
|
26
23
|
```bash
|
|
27
|
-
# From npm (recommended)
|
|
28
24
|
openclaw plugins install @akilles/soundcloud-watcher
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
git clone https://github.com/wlinds/openclaw-soundcloud-watcher
|
|
32
|
-
openclaw plugins install -l ./openclaw-soundcloud-watcher/openclaw-soundcloud-watcher
|
|
25
|
+
openclaw plugins enable soundcloud-watcher
|
|
26
|
+
openclaw gateway restart
|
|
33
27
|
```
|
|
34
28
|
|
|
35
|
-
|
|
36
29
|
### 2. Get SoundCloud Credentials
|
|
37
30
|
|
|
38
31
|
1. Log into SoundCloud
|
|
39
32
|
2. Go to [soundcloud.com/you/apps](https://soundcloud.com/you/apps)
|
|
40
33
|
3. Click "Register a new application"
|
|
41
|
-
4. Fill in name and website
|
|
42
|
-
5. Copy your **Client ID** and **Client Secret**
|
|
34
|
+
4. Fill in name and website (any URL works)
|
|
35
|
+
5. Copy your **Client ID** and **Client Secret**
|
|
43
36
|
|
|
44
37
|
### 3. Configure
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
Create the credentials file:
|
|
47
40
|
|
|
48
41
|
```bash
|
|
49
|
-
/soundcloud
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Then edit `~/.openclaw/openclaw.json` and paste your credentials:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"plugins": {
|
|
57
|
-
"enabled": true,
|
|
58
|
-
"entries": {
|
|
59
|
-
"soundcloud-watcher": {
|
|
60
|
-
"enabled": true,
|
|
61
|
-
"config": {
|
|
62
|
-
"clientId": "YOUR_CLIENT_ID",
|
|
63
|
-
"clientSecret": "YOUR_CLIENT_SECRET",
|
|
64
|
-
"username": "your_soundcloud_username",
|
|
65
|
-
"checkIntervalHours": 6,
|
|
66
|
-
"myTracksLimit": 10,
|
|
67
|
-
"dormantDays": 90,
|
|
68
|
-
"sessionKey": "agent:main:main"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
42
|
+
nano ~/.openclaw/secrets/soundcloud.env
|
|
74
43
|
```
|
|
75
44
|
|
|
76
|
-
|
|
45
|
+
Add your credentials:
|
|
77
46
|
|
|
78
|
-
```bash
|
|
79
|
-
openclaw gateway restart
|
|
80
|
-
openclaw plugins list # Should show soundcloud-watcher
|
|
81
|
-
/soundcloud-status # Should show your account info
|
|
82
47
|
```
|
|
48
|
+
SOUNDCLOUD_CLIENT_ID=your_client_id
|
|
49
|
+
SOUNDCLOUD_CLIENT_SECRET=your_client_secret
|
|
50
|
+
MY_USERNAME=your_soundcloud_username
|
|
83
51
|
|
|
84
|
-
|
|
52
|
+
# Optional settings
|
|
53
|
+
INCLUDE_LINKS=true # Include URLs in notifications (default: true)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. Restart & Verify
|
|
85
57
|
|
|
86
58
|
```bash
|
|
87
|
-
|
|
88
|
-
/soundcloud-add noisia
|
|
89
|
-
/soundcloud-list
|
|
59
|
+
openclaw gateway restart
|
|
90
60
|
```
|
|
91
61
|
|
|
92
|
-
|
|
62
|
+
Then in chat:
|
|
63
|
+
```
|
|
64
|
+
/soundcloud-setup # Should show "Already configured!"
|
|
65
|
+
/soundcloud-status # Should show your account info
|
|
66
|
+
```
|
|
93
67
|
|
|
94
68
|
## Commands
|
|
95
69
|
|
|
96
70
|
| Command | Description |
|
|
97
71
|
|---------|-------------|
|
|
98
|
-
| `/soundcloud-setup` |
|
|
72
|
+
| `/soundcloud-setup` | Show setup instructions and config status |
|
|
99
73
|
| `/soundcloud-status` | Show tracking status and account info |
|
|
100
|
-
| `/soundcloud-check` | Run immediate check (
|
|
74
|
+
| `/soundcloud-check` | Run immediate check (verbose output) |
|
|
75
|
+
| `/soundcloud-cron` | Run check for automation (silent if no updates) |
|
|
101
76
|
| `/soundcloud-add <username>` | Track artist(s) - space-separated |
|
|
102
|
-
| `/soundcloud-remove <username>` |
|
|
77
|
+
| `/soundcloud-remove <username>` | Stop tracking an artist |
|
|
103
78
|
| `/soundcloud-list` | List all tracked artists |
|
|
104
79
|
|
|
105
|
-
##
|
|
106
|
-
|
|
107
|
-
All options in `~/.openclaw/openclaw.json` under `plugins.entries.soundcloud-watcher.config`:
|
|
108
|
-
|
|
109
|
-
| Option | Type | Required | Default | Description |
|
|
110
|
-
|--------|------|----------|---------|-------------|
|
|
111
|
-
| `enabled` | boolean | No | true | Enable/disable watcher |
|
|
112
|
-
| `clientId` | string | Yes | - | SoundCloud API Client ID |
|
|
113
|
-
| `clientSecret` | string | Yes | - | SoundCloud API Client Secret |
|
|
114
|
-
| `username` | string | Yes | - | Your SoundCloud username |
|
|
115
|
-
| `checkIntervalHours` | number | No | 6 | Hours between automatic checks |
|
|
116
|
-
| `myTracksLimit` | number | No | 10 | Number of your tracks to monitor |
|
|
117
|
-
| `dormantDays` | number | No | 90 | Days before artist is considered dormant |
|
|
118
|
-
| `sessionKey` | string | No | `agent:main:main` | OpenClaw session for notifications |
|
|
80
|
+
## Automated Checking
|
|
119
81
|
|
|
120
|
-
|
|
82
|
+
The plugin responds to commands but doesn't auto-poll. Set up a cron job for automatic notifications:
|
|
121
83
|
|
|
122
|
-
|
|
84
|
+
```bash
|
|
85
|
+
openclaw cron add --name "soundcloud-check" \
|
|
86
|
+
--every 6h \
|
|
87
|
+
--isolated \
|
|
88
|
+
--message "Run /soundcloud-cron and forward any updates to me on Telegram."
|
|
89
|
+
```
|
|
123
90
|
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
91
|
+
Uses `/soundcloud-cron` which:
|
|
92
|
+
- Returns updates only (silent if nothing new)
|
|
93
|
+
- Logs errors but doesn't spam on config issues
|
|
127
94
|
|
|
128
|
-
|
|
95
|
+
**Alternative:** Add to your `HEARTBEAT.md`:
|
|
96
|
+
```markdown
|
|
97
|
+
- [ ] Run /soundcloud-cron if not checked in last 6 hours
|
|
98
|
+
```
|
|
129
99
|
|
|
130
|
-
|
|
100
|
+
## File Locations
|
|
131
101
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- **Backoff state:** `~/.openclaw/soundcloud_backoff.json`
|
|
102
|
+
| File | Purpose |
|
|
103
|
+
|------|---------|
|
|
104
|
+
| `~/.openclaw/secrets/soundcloud.env` | Your API credentials |
|
|
105
|
+
| `~/.openclaw/data/artists.json` | Tracked artists data |
|
|
106
|
+
| `~/.openclaw/data/soundcloud_tracking.json` | Your account tracking data |
|
|
138
107
|
|
|
139
108
|
## Troubleshooting
|
|
140
109
|
|
|
141
|
-
###
|
|
110
|
+
### "Not configured" error
|
|
142
111
|
|
|
143
|
-
|
|
144
|
-
openclaw plugins list
|
|
145
|
-
openclaw gateway logs
|
|
146
|
-
```
|
|
112
|
+
Check your credentials file exists and has correct format:
|
|
147
113
|
|
|
148
|
-
Check that:
|
|
149
|
-
- Plugin shows as `enabled: true` in list
|
|
150
|
-
- Gateway logs don't show errors
|
|
151
|
-
|
|
152
|
-
Verify plugin directory exists:
|
|
153
114
|
```bash
|
|
154
|
-
|
|
115
|
+
cat ~/.openclaw/secrets/soundcloud.env
|
|
155
116
|
```
|
|
156
117
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
### No notifications
|
|
118
|
+
Should contain:
|
|
119
|
+
```
|
|
120
|
+
SOUNDCLOUD_CLIENT_ID=...
|
|
121
|
+
SOUNDCLOUD_CLIENT_SECRET=...
|
|
122
|
+
MY_USERNAME=...
|
|
123
|
+
```
|
|
165
124
|
|
|
166
|
-
|
|
125
|
+
### Plugin not loading
|
|
167
126
|
|
|
168
|
-
Verify gateway is running:
|
|
169
127
|
```bash
|
|
170
|
-
openclaw
|
|
128
|
+
openclaw plugins list
|
|
171
129
|
```
|
|
172
130
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Run `/soundcloud-setup` for detailed instructions with current config status.
|
|
131
|
+
Should show `soundcloud-watcher` as `loaded`. If `disabled`:
|
|
176
132
|
|
|
177
|
-
## Updating
|
|
178
|
-
|
|
179
|
-
If installed via symlink (`-l`):
|
|
180
133
|
```bash
|
|
181
|
-
|
|
182
|
-
git pull
|
|
134
|
+
openclaw plugins enable soundcloud-watcher
|
|
183
135
|
openclaw gateway restart
|
|
184
136
|
```
|
|
185
137
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
openclaw gateway restart
|
|
190
|
-
```
|
|
138
|
+
### API rate limits
|
|
139
|
+
|
|
140
|
+
The plugin handles rate limits automatically with exponential backoff. If issues persist, wait a few minutes and try again.
|
|
191
141
|
|
|
192
142
|
## Uninstalling
|
|
193
143
|
|
|
194
144
|
```bash
|
|
195
145
|
openclaw plugins disable soundcloud-watcher
|
|
196
|
-
|
|
146
|
+
rm -rf ~/.openclaw/extensions/soundcloud-watcher
|
|
197
147
|
```
|
|
198
148
|
|
|
199
|
-
|
|
149
|
+
Optionally remove data:
|
|
200
150
|
```bash
|
|
201
|
-
rm
|
|
202
|
-
rm
|
|
203
|
-
rm
|
|
204
|
-
rm -rf ~/.openclaw/soundcloud_backoff.json
|
|
151
|
+
rm ~/.openclaw/secrets/soundcloud.env
|
|
152
|
+
rm ~/.openclaw/data/artists.json
|
|
153
|
+
rm ~/.openclaw/data/soundcloud_tracking.json
|
|
205
154
|
```
|
|
206
155
|
|
|
207
|
-
##
|
|
156
|
+
## Links
|
|
208
157
|
|
|
209
158
|
- **GitHub:** https://github.com/wlinds/openclaw-soundcloud-watcher
|
|
210
|
-
- **
|
|
159
|
+
- **npm:** https://www.npmjs.com/package/@akilles/soundcloud-watcher
|
|
211
160
|
- **OpenClaw Docs:** https://docs.openclaw.ai/plugin
|
|
212
161
|
|
|
213
162
|
## License
|
|
214
163
|
|
|
215
|
-
MIT
|
|
164
|
+
MIT
|
package/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface PluginConfig {
|
|
|
10
10
|
checkIntervalHours: number;
|
|
11
11
|
myTracksLimit: number;
|
|
12
12
|
dormantDays: number;
|
|
13
|
+
includeLinks: boolean;
|
|
13
14
|
sessionKey?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -34,6 +35,7 @@ function loadConfig(): PluginConfig | null {
|
|
|
34
35
|
checkIntervalHours: 6,
|
|
35
36
|
myTracksLimit: 10,
|
|
36
37
|
dormantDays: 90,
|
|
38
|
+
includeLinks: env.INCLUDE_LINKS !== 'false', // Default: true
|
|
37
39
|
sessionKey: 'agent:main:main',
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -57,6 +59,7 @@ export default function register(api: any) {
|
|
|
57
59
|
username: config.username,
|
|
58
60
|
myTracksLimit: config.myTracksLimit,
|
|
59
61
|
dormantDays: config.dormantDays,
|
|
62
|
+
includeLinks: config.includeLinks,
|
|
60
63
|
logger: (...args: any[]) => logger.debug?.(...args) || console.log(...args),
|
|
61
64
|
});
|
|
62
65
|
return watcher;
|
|
@@ -178,5 +181,29 @@ MY_USERNAME=your_soundcloud_username
|
|
|
178
181
|
},
|
|
179
182
|
});
|
|
180
183
|
|
|
184
|
+
api.registerCommand({
|
|
185
|
+
name: 'soundcloud-cron',
|
|
186
|
+
description: 'Run check for cron (only outputs if there are updates)',
|
|
187
|
+
handler: async () => {
|
|
188
|
+
const w = getWatcher();
|
|
189
|
+
if (!w) {
|
|
190
|
+
(logger.warn ?? console.warn).call(logger, 'SoundCloud cron: not configured');
|
|
191
|
+
return { text: '' }; // Silent fail for cron
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const result = await w.runCron();
|
|
195
|
+
if (result) {
|
|
196
|
+
return { text: result };
|
|
197
|
+
}
|
|
198
|
+
// No updates - return empty (silent success)
|
|
199
|
+
(logger.debug ?? console.log).call(logger, 'SoundCloud cron: no updates');
|
|
200
|
+
return { text: '' };
|
|
201
|
+
} catch (e) {
|
|
202
|
+
(logger.error ?? console.error).call(logger, 'SoundCloud cron error:', e);
|
|
203
|
+
return { text: `SoundCloud check failed: ${e}` };
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
181
208
|
(logger.info ?? console.log).call(logger, 'SoundCloud Watcher plugin loaded');
|
|
182
209
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/soundcloud_watcher.ts
CHANGED
|
@@ -56,12 +56,24 @@ export interface SoundCloudWatcherConfig {
|
|
|
56
56
|
username: string;
|
|
57
57
|
myTracksLimit?: number;
|
|
58
58
|
dormantDays?: number;
|
|
59
|
+
includeLinks?: boolean; // Include URLs in notifications (default: true)
|
|
59
60
|
logger?: (...args: any[]) => void;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
interface UserInfo {
|
|
63
64
|
username: string;
|
|
64
65
|
display_name: string;
|
|
66
|
+
permalink_url?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface FollowerNotification {
|
|
70
|
+
type: 'new' | 'lost';
|
|
71
|
+
users: UserInfo[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface AccountNotifications {
|
|
75
|
+
followers: FollowerNotification[];
|
|
76
|
+
engagement: string[]; // likes, reposts, etc.
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
interface TrackStats {
|
|
@@ -396,9 +408,11 @@ class SoundCloudAPI {
|
|
|
396
408
|
|
|
397
409
|
for (const f of data.collection ?? []) {
|
|
398
410
|
if (f && typeof f === "object" && "id" in f) {
|
|
411
|
+
const permalink = f.permalink ?? f.username ?? "unknown";
|
|
399
412
|
followers[String(f.id)] = {
|
|
400
|
-
username:
|
|
413
|
+
username: permalink,
|
|
401
414
|
display_name: f.full_name ?? f.username ?? "unknown",
|
|
415
|
+
permalink_url: f.permalink_url ?? `https://soundcloud.com/${permalink}`,
|
|
402
416
|
};
|
|
403
417
|
}
|
|
404
418
|
}
|
|
@@ -442,12 +456,12 @@ class AccountWatcher {
|
|
|
442
456
|
writeJson(ACCOUNT_DATA, this.data);
|
|
443
457
|
}
|
|
444
458
|
|
|
445
|
-
async check(): Promise<
|
|
446
|
-
const
|
|
459
|
+
async check(): Promise<AccountNotifications> {
|
|
460
|
+
const result: AccountNotifications = { followers: [], engagement: [] };
|
|
447
461
|
|
|
448
462
|
if (!this.data.my_account) {
|
|
449
463
|
const user = await this.api.resolve(this.config.myUsername);
|
|
450
|
-
if (!user) return
|
|
464
|
+
if (!user) return result;
|
|
451
465
|
this.data.my_account = {
|
|
452
466
|
user_id: user.id,
|
|
453
467
|
username: user.permalink ?? this.config.myUsername,
|
|
@@ -459,7 +473,7 @@ class AccountWatcher {
|
|
|
459
473
|
const profile = await this.api.getUser(userId);
|
|
460
474
|
if (!profile) {
|
|
461
475
|
this.log("Failed to fetch profile, skipping account check");
|
|
462
|
-
return
|
|
476
|
+
return result;
|
|
463
477
|
}
|
|
464
478
|
|
|
465
479
|
const currentCount = num(profile.followers_count);
|
|
@@ -479,23 +493,16 @@ class AccountWatcher {
|
|
|
479
493
|
if (Object.keys(stored).length) {
|
|
480
494
|
const newFollowers = Object.entries(currentFollowers)
|
|
481
495
|
.filter(([uid]) => !stored[uid])
|
|
482
|
-
.map(([, f]) => f
|
|
496
|
+
.map(([, f]) => f);
|
|
483
497
|
const lostFollowers = Object.entries(stored)
|
|
484
498
|
.filter(([uid]) => !currentFollowers[uid])
|
|
485
|
-
.map(([, f]) => f
|
|
499
|
+
.map(([, f]) => f);
|
|
486
500
|
|
|
487
501
|
if (newFollowers.length) {
|
|
488
|
-
|
|
489
|
-
if (newFollowers.length > 3) names += ` +${newFollowers.length - 3} more`;
|
|
490
|
-
notifications.push(
|
|
491
|
-
`New follower${newFollowers.length > 1 ? "s" : ""}: **${names}**`
|
|
492
|
-
);
|
|
502
|
+
result.followers.push({ type: 'new', users: newFollowers });
|
|
493
503
|
}
|
|
494
504
|
if (lostFollowers.length) {
|
|
495
|
-
|
|
496
|
-
notifications.push(
|
|
497
|
-
`Lost follower${lostFollowers.length > 1 ? "s" : ""}: ${names}`
|
|
498
|
-
);
|
|
505
|
+
result.followers.push({ type: 'lost', users: lostFollowers });
|
|
499
506
|
}
|
|
500
507
|
}
|
|
501
508
|
|
|
@@ -550,11 +557,11 @@ class AccountWatcher {
|
|
|
550
557
|
let names = newLikerNames.slice(0, 3).join(", ");
|
|
551
558
|
if (newLikerNames.length > 3)
|
|
552
559
|
names += ` +${newLikerNames.length - 3} more`;
|
|
553
|
-
|
|
560
|
+
result.engagement.push(`**${names}** liked '${title}'`);
|
|
554
561
|
}
|
|
555
562
|
if (unlikerNames.length) {
|
|
556
563
|
const names = unlikerNames.slice(0, 3).join(", ");
|
|
557
|
-
|
|
564
|
+
result.engagement.push(`${names} unliked '${title}'`);
|
|
558
565
|
}
|
|
559
566
|
} else {
|
|
560
567
|
stats.likers = prevLikers;
|
|
@@ -562,7 +569,7 @@ class AccountWatcher {
|
|
|
562
569
|
|
|
563
570
|
const newReposts = currentReposts - (prev.reposts ?? 0);
|
|
564
571
|
if (newReposts > 0) {
|
|
565
|
-
|
|
572
|
+
result.engagement.push(
|
|
566
573
|
`'${title}' got ${newReposts} repost${newReposts > 1 ? "s" : ""}!`
|
|
567
574
|
);
|
|
568
575
|
}
|
|
@@ -579,7 +586,7 @@ class AccountWatcher {
|
|
|
579
586
|
}
|
|
580
587
|
|
|
581
588
|
this.save();
|
|
582
|
-
return
|
|
589
|
+
return result;
|
|
583
590
|
}
|
|
584
591
|
}
|
|
585
592
|
|
|
@@ -764,12 +771,14 @@ export class SoundCloudWatcher {
|
|
|
764
771
|
private api: SoundCloudAPI;
|
|
765
772
|
private myTracksLimit: number;
|
|
766
773
|
private dormantDays: number;
|
|
774
|
+
private includeLinks: boolean;
|
|
767
775
|
private log: (...args: any[]) => void;
|
|
768
776
|
|
|
769
777
|
constructor(opts: SoundCloudWatcherConfig) {
|
|
770
778
|
this.log = opts.logger ?? console.log;
|
|
771
779
|
this.myTracksLimit = opts.myTracksLimit ?? 10;
|
|
772
780
|
this.dormantDays = opts.dormantDays ?? 90;
|
|
781
|
+
this.includeLinks = opts.includeLinks ?? true; // Default: include links
|
|
773
782
|
this.config = new Config(opts.clientId, opts.clientSecret, opts.username);
|
|
774
783
|
this.api = new SoundCloudAPI(this.config, this.log);
|
|
775
784
|
}
|
|
@@ -827,11 +836,24 @@ export class SoundCloudWatcher {
|
|
|
827
836
|
lines.push(`[${utcnow()}] Full check complete\n`);
|
|
828
837
|
|
|
829
838
|
lines.push("--- Account ---");
|
|
830
|
-
for (const
|
|
831
|
-
|
|
839
|
+
for (const fn of accountNotifs.followers) {
|
|
840
|
+
lines.push(...this.formatFollowerNotification(fn).map(l => ` ${l}`));
|
|
841
|
+
}
|
|
842
|
+
for (const e of accountNotifs.engagement) {
|
|
843
|
+
lines.push(` ${e}`);
|
|
844
|
+
}
|
|
845
|
+
if (!accountNotifs.followers.length && !accountNotifs.engagement.length) {
|
|
846
|
+
lines.push(" No updates");
|
|
847
|
+
}
|
|
832
848
|
|
|
833
849
|
lines.push("\n--- Artist Releases ---");
|
|
834
|
-
for (const r of releases)
|
|
850
|
+
for (const r of releases) {
|
|
851
|
+
if (this.includeLinks && r.url) {
|
|
852
|
+
lines.push(` ${r.artist}: ${r.title} - ${r.url}`);
|
|
853
|
+
} else {
|
|
854
|
+
lines.push(` ${r.artist}: ${r.title}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
835
857
|
if (!releases.length) lines.push(" No new releases");
|
|
836
858
|
|
|
837
859
|
lines.push(`\nAPI calls: ${this.api.calls}`);
|
|
@@ -868,6 +890,34 @@ export class SoundCloudWatcher {
|
|
|
868
890
|
return tracker.list();
|
|
869
891
|
}
|
|
870
892
|
|
|
893
|
+
private formatFollowerNotification(notif: FollowerNotification): string[] {
|
|
894
|
+
const lines: string[] = [];
|
|
895
|
+
const users = notif.users.slice(0, 5); // Max 5 users shown
|
|
896
|
+
const remaining = notif.users.length - users.length;
|
|
897
|
+
|
|
898
|
+
if (notif.type === 'new') {
|
|
899
|
+
lines.push(`New follower${notif.users.length > 1 ? 's' : ''}:`);
|
|
900
|
+
for (const u of users) {
|
|
901
|
+
if (this.includeLinks && u.permalink_url) {
|
|
902
|
+
lines.push(`- **${u.display_name}**: ${u.permalink_url}`);
|
|
903
|
+
} else {
|
|
904
|
+
lines.push(`- **${u.display_name}**`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
lines.push(`Lost follower${notif.users.length > 1 ? 's' : ''}:`);
|
|
909
|
+
for (const u of users) {
|
|
910
|
+
lines.push(`- ${u.display_name}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (remaining > 0) {
|
|
915
|
+
lines.push(` ...and ${remaining} more`);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return lines;
|
|
919
|
+
}
|
|
920
|
+
|
|
871
921
|
async runCron(): Promise<string | null> {
|
|
872
922
|
const tokenErr = await this.ensureToken();
|
|
873
923
|
if (tokenErr) {
|
|
@@ -884,16 +934,31 @@ export class SoundCloudWatcher {
|
|
|
884
934
|
]);
|
|
885
935
|
|
|
886
936
|
const lines: string[] = [];
|
|
887
|
-
|
|
937
|
+
|
|
938
|
+
// Format follower notifications
|
|
939
|
+
const hasFollowerUpdates = accountNotifs.followers.length > 0;
|
|
940
|
+
const hasEngagement = accountNotifs.engagement.length > 0;
|
|
941
|
+
|
|
942
|
+
if (hasFollowerUpdates || hasEngagement) {
|
|
888
943
|
lines.push("**Account:**");
|
|
889
|
-
|
|
944
|
+
for (const fn of accountNotifs.followers) {
|
|
945
|
+
lines.push(...this.formatFollowerNotification(fn));
|
|
946
|
+
}
|
|
947
|
+
for (const e of accountNotifs.engagement) {
|
|
948
|
+
lines.push(`- ${e}`);
|
|
949
|
+
}
|
|
890
950
|
lines.push("");
|
|
891
951
|
}
|
|
952
|
+
|
|
892
953
|
if (releases.length) {
|
|
893
954
|
lines.push("**New Releases:**");
|
|
894
955
|
for (const r of releases) {
|
|
895
|
-
|
|
896
|
-
|
|
956
|
+
if (this.includeLinks && r.url) {
|
|
957
|
+
lines.push(`- **${r.artist}** dropped: ${r.title}`);
|
|
958
|
+
lines.push(` ${r.url}`);
|
|
959
|
+
} else {
|
|
960
|
+
lines.push(`- **${r.artist}** dropped: ${r.title}`);
|
|
961
|
+
}
|
|
897
962
|
}
|
|
898
963
|
lines.push("");
|
|
899
964
|
}
|