this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #10 from HenrickTheBull/Update-Atmosphere-Handling

[Fix] Discord webhook integration by adding configuration checks for …

authored by

Henrick and committed by
GitHub
59074183 2c6b8805

+144 -22
+6 -1
bot/discordWebhook.js
··· 10 10 class DiscordWebhook { 11 11 constructor() { 12 12 this.webhookUrl = config.discord?.webhookUrl; 13 + this.enabled = config.discord?.enabled; 14 + 13 15 if (!this.webhookUrl) { 14 16 console.warn('Discord webhook URL not configured. Discord integration disabled.'); 17 + } else if (this.enabled === false) { 18 + console.log('Discord webhook URL is configured but integration is disabled in settings.'); 15 19 } else { 16 20 console.log('Discord webhook integration initialized.'); 17 21 } ··· 22 26 * @returns {boolean} - Whether Discord integration is enabled 23 27 */ 24 28 isEnabled() { 25 - return !!this.webhookUrl; 29 + // Only return true if both the webhook URL is set AND explicitly enabled in config 30 + return !!this.webhookUrl && this.enabled !== false; 26 31 } 27 32 28 33 /**
+138 -21
scrapers/blueskyScraper.js
··· 12 12 // Initialize with service endpoint only - no authentication needed for public posts 13 13 this.serviceEndpoint = config.bluesky.service || 'https://bsky.social'; 14 14 // Update regex to support bsky.app, deer.social, and sky.thebull.app 15 - this.matcher = new RegExp('(?:https?://)?(?:bsky\\.app|deer\\.social|sky\\.thebull\\.app)/profile/(?<repo>\\S+)/post/(?<rkey>\\S+)'); 15 + // Also properly handle DIDs in the URL path 16 + this.matcher = new RegExp('(?:https?://)?(?:bsky\\.app|deer\\.social|sky\\.thebull\\.app)/profile/(?<repo>[^/]+(?:/[^/]+)?)/post/(?<rkey>[^/]+)'); 16 17 17 18 // Initialize the agent 18 19 this.agent = new BskyAgent({ service: this.serviceEndpoint }); ··· 29 30 } 30 31 31 32 /** 32 - * Parse a Bluesky URL to extract handle and rkey (post ID) 33 + * Parse a Bluesky URL to extract handle/DID and rkey (post ID) 33 34 * @param {string} url - Bluesky URL 34 - * @returns {{repo: string, rkey: string}} - Extracted repo (handle) and rkey 35 + * @returns {{repo: string, rkey: string}} - Extracted repo (handle/DID) and rkey 35 36 */ 36 37 parseBlueskyUrl(url) { 37 38 try { ··· 40 41 throw new Error('Invalid Bluesky URL format'); 41 42 } 42 43 44 + let repo = matches.groups.repo; 45 + 46 + // Special handling for DIDs in the URL 47 + // Example: did:plc:pmrzhfxlsflj5vb63h27jmax 48 + if (repo.includes('/')) { 49 + // This might be a URL-encoded DID, so extract just the DID part 50 + const didMatches = repo.match(/^(did:[^/]+)/) || repo.match(/^([^/]+\/[^/]+)$/); 51 + if (didMatches && didMatches[1]) { 52 + repo = didMatches[1]; 53 + } 54 + } 55 + 43 56 return { 44 - repo: matches.groups.repo, 57 + repo: repo, 45 58 rkey: matches.groups.rkey 46 59 }; 47 60 } catch (error) { ··· 50 63 } 51 64 52 65 /** 53 - * Get user's display name from their handle 54 - * @param {string} handle - The user's Bluesky handle 55 - * @returns {Promise<string>} - User's display name or handle if not found 66 + * Get user's display name from their handle or DID 67 + * @param {string} identifier - The user's Bluesky handle or DID 68 + * @returns {Promise<string>} - User's display name or original identifier if not found 56 69 */ 57 - async getUserDisplayName(handle) { 70 + async getUserDisplayName(identifier) { 58 71 try { 59 - // Try to get user info from API 60 - const response = await axios.get(`${this.serviceEndpoint}/xrpc/app.bsky.actor.getProfile`, { 61 - params: { actor: handle }, 62 - headers: { 'User-Agent': 'Stagehand/1.1.0' } 63 - }); 72 + let did = identifier; 64 73 65 - if (response.data && response.data.displayName) { 66 - return response.data.displayName; 74 + // Check if the identifier is a handle (contains a dot) rather than a DID 75 + if (identifier.includes('.') && !identifier.startsWith('did:')) { 76 + try { 77 + // Convert handle to DID 78 + const didResponse = await axios.get(`${this.serviceEndpoint}/xrpc/com.atproto.identity.resolveHandle`, { 79 + params: { handle: identifier }, 80 + headers: { 'User-Agent': 'Stagehand/1.1.0' } 81 + }); 82 + 83 + if (didResponse.data && didResponse.data.did) { 84 + did = didResponse.data.did; 85 + } else { 86 + console.log(`Failed to resolve handle to DID: ${identifier}`); 87 + return identifier; 88 + } 89 + } catch (handleError) { 90 + console.log(`Error resolving handle ${identifier}: ${handleError.message}`); 91 + return identifier; 92 + } 67 93 } 68 94 69 - // Return handle if display name not found 70 - return handle; 95 + // Now we have a DID (either provided or resolved from handle) 96 + try { 97 + // Access the profile directly using repo API - this is the most reliable method 98 + const profileUrl = new URL(`${this.serviceEndpoint}/xrpc/com.atproto.repo.getRecord`); 99 + profileUrl.searchParams.append('repo', did); 100 + profileUrl.searchParams.append('collection', 'app.bsky.actor.profile'); 101 + profileUrl.searchParams.append('rkey', 'self'); 102 + 103 + const profileResponse = await axios.get(profileUrl.toString(), { 104 + headers: { 'User-Agent': 'Stagehand/1.1.0' } 105 + }); 106 + 107 + if (profileResponse.data && 108 + profileResponse.data.value && 109 + profileResponse.data.value.displayName) { 110 + return profileResponse.data.value.displayName; 111 + } 112 + 113 + // If we got a profile but no displayName, try to get the handle 114 + if (profileResponse.data && 115 + profileResponse.data.value && 116 + profileResponse.data.value.handle) { 117 + return profileResponse.data.value.handle; 118 + } 119 + } catch (profileError) { 120 + console.log(`Error getting profile for ${did}: ${profileError.message}`); 121 + 122 + // If that fails, try the actor getProfile API as a fallback 123 + try { 124 + const actorResponse = await axios.get(`${this.serviceEndpoint}/xrpc/app.bsky.actor.getProfile`, { 125 + params: { actor: did }, 126 + headers: { 'User-Agent': 'Stagehand/1.1.0' } 127 + }); 128 + 129 + if (actorResponse.data && actorResponse.data.displayName) { 130 + return actorResponse.data.displayName; 131 + } 132 + 133 + if (actorResponse.data && actorResponse.data.handle) { 134 + return actorResponse.data.handle; 135 + } 136 + } catch (actorError) { 137 + console.log(`Error with actor getProfile API for ${did}: ${actorError.message}`); 138 + } 139 + } 140 + 141 + // Return the original identifier if all methods fail 142 + return identifier; 71 143 } catch (error) { 72 - console.log(`Couldn't get display name for ${handle}: ${error.message}`); 73 - return handle; // Fallback to handle on error 144 + console.log(`Couldn't get display name for ${identifier}: ${error.message}`); 145 + return identifier; // Fallback to identifier on error 74 146 } 75 147 } 76 148 ··· 283 355 } 284 356 285 357 // 5. Get user's display name 286 - const displayName = await this.getUserDisplayName(repo); 287 - console.log(`Using display name: ${displayName} for handle: ${repo}`); 358 + // First try to get the author's profile from the record itself if present 359 + let displayName = repo; // Default to repo ID if we can't get a handle/display name 360 + let handle = repo; 361 + try { 362 + // Try to get profile information directly from the repo 363 + const profileUrl = new URL(`${this.serviceEndpoint}/xrpc/com.atproto.repo.getRecord`); 364 + profileUrl.searchParams.append('repo', did); 365 + profileUrl.searchParams.append('collection', 'app.bsky.actor.profile'); 366 + profileUrl.searchParams.append('rkey', 'self'); 367 + 368 + const profileResponse = await axios.get(profileUrl.toString(), { 369 + headers: { 'User-Agent': 'Stagehand/1.1.0' } 370 + }); 371 + 372 + if (profileResponse.data && profileResponse.data.value) { 373 + // Extract display name and handle from the profile 374 + if (profileResponse.data.value.displayName) { 375 + displayName = profileResponse.data.value.displayName; 376 + } 377 + if (profileResponse.data.value.handle) { 378 + handle = profileResponse.data.value.handle; 379 + } 380 + } 381 + } catch (profileError) { 382 + console.log(`Couldn't get profile directly: ${profileError.message}`); 383 + // Fall back to resolving DID to handle 384 + try { 385 + const didToHandleUrl = new URL(`${this.serviceEndpoint}/xrpc/com.atproto.identity.resolveHandle`); 386 + didToHandleUrl.searchParams.append('handle', did); 387 + 388 + const handleResponse = await axios.get(didToHandleUrl.toString(), { 389 + headers: { 'User-Agent': 'Stagehand/1.1.0' } 390 + }); 391 + 392 + if (handleResponse.data && handleResponse.data.did) { 393 + // We have a valid DID, now try to get the profile 394 + const resolvedDisplayName = await this.getUserDisplayName(handleResponse.data.did); 395 + if (resolvedDisplayName && resolvedDisplayName !== handleResponse.data.did) { 396 + displayName = resolvedDisplayName; 397 + } 398 + } 399 + } catch (didError) { 400 + console.log(`Couldn't resolve DID to handle: ${didError.message}`); 401 + } 402 + } 403 + 404 + console.log(`Using display name: ${displayName} for handle: ${handle}`); 288 405 289 406 // 6. Process based on embed type 290 407 const embedType = record?.value?.embed?.$type;