(data: {
proposerUserId: string;
title: string;
excerpt?: string;
subtitle?: string;
workingGroupSlug: string;
postSlug: string;
contentType: 'article' | 'link';
isMembersOnly: boolean;
})
| 664 | * - Slack is not configured |
| 665 | */ |
| 666 | export async function sendSocialAmplificationDM(data: { |
| 667 | proposerUserId: string; |
| 668 | title: string; |
| 669 | excerpt?: string; |
| 670 | subtitle?: string; |
| 671 | workingGroupSlug: string; |
| 672 | postSlug: string; |
| 673 | contentType: 'article' | 'link'; |
| 674 | isMembersOnly: boolean; |
| 675 | }): Promise<boolean> { |
| 676 | // Only articles, not link posts |
| 677 | if (data.contentType === 'link') { |
| 678 | logger.debug({ postSlug: data.postSlug }, 'Skipping social amplification DM for link post'); |
| 679 | return false; |
| 680 | } |
| 681 | |
| 682 | // Not for members-only content |
| 683 | if (data.isMembersOnly) { |
| 684 | logger.debug({ postSlug: data.postSlug }, 'Skipping social amplification DM for members-only post'); |
| 685 | return false; |
| 686 | } |
| 687 | |
| 688 | if (!isSlackConfigured()) { |
| 689 | logger.debug('Slack not configured, skipping social amplification DM'); |
| 690 | return false; |
| 691 | } |
| 692 | |
| 693 | // Look up the author's Slack user ID via the mapping table |
| 694 | const slackDb = new SlackDatabase(); |
| 695 | const mapping = await slackDb.getByWorkosUserId(data.proposerUserId); |
| 696 | |
| 697 | if (!mapping?.slack_user_id) { |
| 698 | logger.debug( |
| 699 | { proposerUserId: data.proposerUserId, postSlug: data.postSlug }, |
| 700 | 'No Slack user mapping for author, skipping social amplification DM' |
| 701 | ); |
| 702 | return false; |
| 703 | } |
| 704 | |
| 705 | const postUrl = `${APP_URL}/working-groups/${data.workingGroupSlug}#post-${data.postSlug}`; |
| 706 | const socialText = data.excerpt || data.subtitle || ''; |
| 707 | |
| 708 | // LinkedIn: use excerpt/subtitle with URL and hashtags |
| 709 | const linkedInPost = `"${socialText}"\n\nRead more: ${postUrl}\n\n#AgenticAdvertising #AdCP #AdTech #AI`; |
| 710 | |
| 711 | // Twitter: title + short excerpt, keep under 280 chars |
| 712 | const tweetBase = `${data.title} — `; |
| 713 | const tweetHashtags = `\n\n${postUrl}\n\n#AgenticAdvertising #AdCP`; |
| 714 | const tweetBudget = 280 - tweetBase.length - tweetHashtags.length; |
| 715 | const tweetExcerpt = tweetBudget > 0 && socialText |
| 716 | ? socialText.length <= tweetBudget |
| 717 | ? socialText |
| 718 | : socialText.slice(0, tweetBudget - 1) + '\u2026' |
| 719 | : ''; |
| 720 | const tweetPost = tweetExcerpt |
| 721 | ? `${tweetBase}${tweetExcerpt}${tweetHashtags}` |
| 722 | : `${data.title}${tweetHashtags}`; |
| 723 |
no test coverage detected