Files
Content-Hunter_BE/src/modules/content/services/platform-adapters.service.ts
Harun CAN fc88faddb9
All checks were successful
Backend Deploy 🚀 / build-and-deploy (push) Successful in 2m1s
main
2026-02-10 12:27:14 +03:00

289 lines
8.8 KiB
TypeScript

// Platform Adapters Service - Transform content for different platforms
// Path: src/modules/content/services/platform-adapters.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { SocialPlatform } from '@prisma/client';
export interface PlatformConfig {
name: string;
maxLength: number;
supportsMedia: boolean;
supportsLinks: boolean;
supportsHashtags: boolean;
supportsMentions: boolean;
supportsEmoji: boolean;
mediaFormats?: string[];
optimalLength?: number;
bestPostingTimes?: string[];
engagementTips?: string[];
}
export const PLATFORM_CONFIGS: Record<SocialPlatform, PlatformConfig> = {
TWITTER: {
name: 'X (Twitter)',
maxLength: 280,
optimalLength: 240,
supportsMedia: true,
supportsLinks: true,
supportsHashtags: true,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['image', 'video', 'gif'],
bestPostingTimes: ['9:00', '12:00', '17:00', '21:00'],
engagementTips: [
'Use threads for longer content',
'Ask questions to boost engagement',
'Quote tweet with commentary',
],
},
LINKEDIN: {
name: 'LinkedIn',
maxLength: 3000,
optimalLength: 1300,
supportsMedia: true,
supportsLinks: true,
supportsHashtags: true,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['image', 'video', 'document', 'carousel'],
bestPostingTimes: ['7:30', '12:00', '17:00'],
engagementTips: [
'Hook in first line (before "see more")',
'Use line breaks for readability',
'End with a question',
],
},
INSTAGRAM: {
name: 'Instagram',
maxLength: 2200,
optimalLength: 1500,
supportsMedia: true,
supportsLinks: false,
supportsHashtags: true,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['image', 'video', 'reels', 'carousel', 'stories'],
bestPostingTimes: ['6:00', '11:00', '19:00'],
engagementTips: [
'Use up to 30 hashtags',
'Put hashtags in first comment',
'Include CTA in caption',
],
},
FACEBOOK: {
name: 'Facebook',
maxLength: 63206,
optimalLength: 250,
supportsMedia: true,
supportsLinks: true,
supportsHashtags: true,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['image', 'video', 'reels', 'stories'],
bestPostingTimes: ['9:00', '13:00', '19:00'],
engagementTips: [
'Shorter posts perform better',
'Native video preferred',
'Use Facebook Live for engagement',
],
},
TIKTOK: {
name: 'TikTok',
maxLength: 300,
optimalLength: 150,
supportsMedia: true,
supportsLinks: false,
supportsHashtags: true,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['video'],
bestPostingTimes: ['7:00', '10:00', '19:00', '23:00'],
engagementTips: [
'Hook in first 3 seconds',
'Use trending sounds',
'Keep videos under 60 seconds',
],
},
YOUTUBE: {
name: 'YouTube',
maxLength: 5000,
optimalLength: 500,
supportsMedia: true,
supportsLinks: true,
supportsHashtags: true,
supportsMentions: false,
supportsEmoji: true,
mediaFormats: ['video', 'shorts'],
bestPostingTimes: ['12:00', '15:00', '19:00'],
engagementTips: [
'Use timestamps in description',
'Include relevant keywords',
'Add cards and end screens',
],
},
PINTEREST: {
name: 'Pinterest',
maxLength: 500,
optimalLength: 300,
supportsMedia: true,
supportsLinks: true,
supportsHashtags: true,
supportsMentions: false,
supportsEmoji: true,
mediaFormats: ['image', 'video', 'idea_pin'],
bestPostingTimes: ['20:00', '21:00', '22:00'],
engagementTips: [
'Vertical images perform best',
'Use keyword-rich descriptions',
'Create multiple pins per post',
],
},
THREADS: {
name: 'Threads',
maxLength: 500,
optimalLength: 280,
supportsMedia: true,
supportsLinks: false,
supportsHashtags: false,
supportsMentions: true,
supportsEmoji: true,
mediaFormats: ['image', 'video'],
bestPostingTimes: ['12:00', '17:00', '21:00'],
engagementTips: [
'Conversational tone works best',
'Reply to trending topics',
'Cross-post from Instagram',
],
},
};
@Injectable()
export class PlatformAdaptersService {
private readonly logger = new Logger(PlatformAdaptersService.name);
/**
* Get platform configuration
*/
getConfig(platform: SocialPlatform): PlatformConfig {
return PLATFORM_CONFIGS[platform];
}
/**
* Get all platform configs
*/
getAllConfigs(): Record<SocialPlatform, PlatformConfig> {
return PLATFORM_CONFIGS;
}
/**
* Adapt content for specific platform
*/
adapt(content: string, sourcePlatform: SocialPlatform, targetPlatform: SocialPlatform): string {
const sourceConfig = PLATFORM_CONFIGS[sourcePlatform];
const targetConfig = PLATFORM_CONFIGS[targetPlatform];
let adapted = content;
// Handle length constraints
if (adapted.length > targetConfig.maxLength) {
adapted = this.truncate(adapted, targetConfig.maxLength);
}
// Handle link support
if (!targetConfig.supportsLinks && sourceConfig.supportsLinks) {
adapted = this.removeLinks(adapted);
}
// Handle hashtag support
if (!targetConfig.supportsHashtags && sourceConfig.supportsHashtags) {
adapted = this.removeHashtags(adapted);
}
return adapted;
}
/**
* Format content for platform
*/
format(content: string, platform: SocialPlatform): string {
const config = PLATFORM_CONFIGS[platform];
switch (platform) {
case 'TWITTER':
return this.formatForTwitter(content, config);
case 'LINKEDIN':
return this.formatForLinkedIn(content, config);
case 'INSTAGRAM':
return this.formatForInstagram(content, config);
default:
return this.truncate(content, config.maxLength);
}
}
/**
* Check if content is valid for platform
*/
validate(content: string, platform: SocialPlatform): { valid: boolean; issues: string[] } {
const config = PLATFORM_CONFIGS[platform];
const issues: string[] = [];
if (content.length > config.maxLength) {
issues.push(`Content exceeds ${config.maxLength} character limit`);
}
if (!config.supportsLinks && this.containsLinks(content)) {
issues.push('Platform does not support links in captions');
}
return { valid: issues.length === 0, issues };
}
// Private helpers
private truncate(content: string, maxLength: number): string {
if (content.length <= maxLength) return content;
return content.substring(0, maxLength - 3) + '...';
}
private removeLinks(content: string): string {
return content.replace(/https?:\/\/[^\s]+/g, '[link in bio]');
}
private removeHashtags(content: string): string {
return content.replace(/#\w+/g, '').replace(/\s+/g, ' ').trim();
}
private containsLinks(content: string): boolean {
return /https?:\/\/[^\s]+/.test(content);
}
private formatForTwitter(content: string, config: PlatformConfig): string {
let formatted = content;
// Ensure hashtags at end
const hashtags = formatted.match(/#\w+/g) || [];
formatted = formatted.replace(/#\w+/g, '').trim();
formatted = `${formatted}\n\n${hashtags.slice(0, 3).join(' ')}`;
return this.truncate(formatted, config.maxLength);
}
private formatForLinkedIn(content: string, config: PlatformConfig): string {
// Add line breaks for readability
const lines = content.split(/(?<=[.!?])\s+/);
return lines.slice(0, 20).join('\n\n');
}
private formatForInstagram(content: string, config: PlatformConfig): string {
let formatted = content;
// Add dots before hashtags (common Instagram style)
if (formatted.includes('#')) {
const mainContent = formatted.split('#')[0].trim();
const hashtags = formatted.match(/#\w+/g) || [];
formatted = `${mainContent}\n.\n.\n.\n${hashtags.join(' ')}`;
}
return formatted;
}
}