@akilles/soundcloud-watcher 2.2.3 → 2.3.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 CHANGED
@@ -9,6 +9,7 @@ Monitor your SoundCloud account and track artist releases. Get notified when som
9
9
  - **New releases** - Get notifications when tracked artists release new music
10
10
  - **Smart API usage** - Only fetches what changed, automatically skips dormant artists
11
11
  - **Rate limit handling** - Exponential backoff for API reliability
12
+ - **OAuth 2.1** - Uses SoundCloud's latest authentication endpoint
12
13
 
13
14
  ## Prerequisites
14
15
 
@@ -85,7 +86,7 @@ The plugin responds to commands but doesn't auto-poll. Set up a cron job for aut
85
86
  openclaw cron add --name "soundcloud-check" \
86
87
  --every 6h \
87
88
  --isolated \
88
- --message "Run /soundcloud-cron and forward any updates to me on Telegram."
89
+ --message "Run /soundcloud-cron and forward any updates to me."
89
90
  ```
90
91
 
91
92
  Uses `/soundcloud-cron` which:
@@ -2,7 +2,7 @@
2
2
  "id": "soundcloud-watcher",
3
3
  "name": "SoundCloud Watcher",
4
4
  "description": "Monitor your SoundCloud account and track artist releases",
5
- "version": "2.2.3",
5
+ "version": "2.3.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akilles/soundcloud-watcher",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
4
4
  "description": "OpenClaw plugin to monitor SoundCloud account and track artist releases",
5
5
  "main": "index.ts",
6
6
  "openclaw": {
@@ -39,13 +39,11 @@
39
39
  "README.md",
40
40
  "LICENSE"
41
41
  ],
42
- "peerDependencies": {
43
- "@types/node": "^22.0.0"
44
- },
45
42
  "engines": {
46
43
  "node": ">=22.0.0"
47
44
  },
48
45
  "devDependencies": {
46
+ "@types/node": "^22.0.0",
49
47
  "typescript": "^5.9.3"
50
48
  }
51
49
  }
@@ -75,8 +75,9 @@ interface UserInfo {
75
75
  }
76
76
 
77
77
  interface FollowerNotification {
78
- type: 'new' | 'lost';
78
+ type: 'new' | 'lost' | 'renamed';
79
79
  users: UserInfo[];
80
+ renames?: { old: UserInfo; new: UserInfo }[]; // Only for type 'renamed'
80
81
  }
81
82
 
82
83
  interface AccountNotifications {
@@ -335,14 +336,19 @@ class SoundCloudAPI {
335
336
 
336
337
  try {
337
338
  this.log("Refreshing token...");
339
+ const basicAuth = Buffer.from(
340
+ `${this.config.clientId}:${this.config.clientSecret}`
341
+ ).toString("base64");
338
342
  const body = new URLSearchParams({
339
343
  grant_type: "client_credentials",
340
- client_id: this.config.clientId,
341
- client_secret: this.config.clientSecret,
342
344
  });
343
- const resp = await fetch(`${API_BASE}/oauth2/token`, {
345
+ const resp = await fetch("https://secure.soundcloud.com/oauth/token", {
344
346
  method: "POST",
345
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
347
+ headers: {
348
+ "Content-Type": "application/x-www-form-urlencoded",
349
+ "Authorization": `Basic ${basicAuth}`,
350
+ "Accept": "application/json; charset=utf-8",
351
+ },
346
352
  body: body.toString(),
347
353
  signal: AbortSignal.timeout(API_TIMEOUT_MS),
348
354
  });
@@ -576,6 +582,19 @@ class AccountWatcher {
576
582
  const lostFollowers = Object.entries(stored)
577
583
  .filter(([uid]) => !currentFollowers[uid])
578
584
  .map(([, f]) => f);
585
+
586
+ // Detect name changes for existing followers
587
+ const renames: { old: UserInfo; new: UserInfo }[] = [];
588
+ for (const [uid, current] of Object.entries(currentFollowers)) {
589
+ const prev = stored[uid];
590
+ if (prev) {
591
+ const nameChanged = prev.username !== current.username ||
592
+ prev.display_name !== current.display_name;
593
+ if (nameChanged) {
594
+ renames.push({ old: prev, new: current });
595
+ }
596
+ }
597
+ }
579
598
 
580
599
  if (newFollowers.length) {
581
600
  result.followers.push({ type: 'new', users: newFollowers });
@@ -583,6 +602,9 @@ class AccountWatcher {
583
602
  if (lostFollowers.length) {
584
603
  result.followers.push({ type: 'lost', users: lostFollowers });
585
604
  }
605
+ if (renames.length) {
606
+ result.followers.push({ type: 'renamed', users: [], renames });
607
+ }
586
608
  }
587
609
 
588
610
  this.data.my_followers = currentFollowers;
@@ -974,20 +996,34 @@ export class SoundCloudWatcher {
974
996
  const users = notif.users.slice(0, 5); // Max 5 users shown
975
997
  const remaining = notif.users.length - users.length;
976
998
 
999
+ // Helper: use display_name if set, otherwise fall back to @username
1000
+ const getName = (u: UserInfo) => u.display_name?.trim() || `@${u.username}`;
1001
+
977
1002
  if (notif.type === 'new') {
978
1003
  lines.push(`New follower${notif.users.length > 1 ? 's' : ''}:`);
979
1004
  for (const u of users) {
980
1005
  if (this.includeLinks && u.permalink_url) {
981
- lines.push(`- **${u.display_name}**: ${u.permalink_url}`);
1006
+ lines.push(`- **${getName(u)}**: ${u.permalink_url}`);
982
1007
  } else {
983
- lines.push(`- **${u.display_name}**`);
1008
+ lines.push(`- **${getName(u)}**`);
984
1009
  }
985
1010
  }
986
- } else {
1011
+ } else if (notif.type === 'lost') {
987
1012
  lines.push(`Lost follower${notif.users.length > 1 ? 's' : ''}:`);
988
1013
  for (const u of users) {
989
- lines.push(`- ${u.display_name}`);
1014
+ lines.push(`- ${getName(u)}`);
1015
+ }
1016
+ } else if (notif.type === 'renamed' && notif.renames?.length) {
1017
+ const renames = notif.renames.slice(0, 5);
1018
+ const renameRemaining = (notif.renames?.length ?? 0) - renames.length;
1019
+ lines.push(`Name change${renames.length > 1 ? 's' : ''}:`);
1020
+ for (const r of renames) {
1021
+ lines.push(`- ${getName(r.old)} → ${getName(r.new)}`);
1022
+ }
1023
+ if (renameRemaining > 0) {
1024
+ lines.push(` ...and ${renameRemaining} more`);
990
1025
  }
1026
+ return lines; // Early return, skip the standard remaining count
991
1027
  }
992
1028
 
993
1029
  if (remaining > 0) {