.
This commit is contained in:
@@ -18,7 +18,6 @@ import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -95,7 +94,7 @@ public class FacebookPostingService {
|
||||
private String postTextOnly(PageCredentials creds, String postText, List<String> hashtags) {
|
||||
validateToken(creds);
|
||||
try {
|
||||
String fullText = buildPostText(postText, hashtags);
|
||||
String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);
|
||||
|
||||
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("access_token", creds.pageToken);
|
||||
@@ -124,7 +123,7 @@ public class FacebookPostingService {
|
||||
private String postWithPhoto(PageCredentials creds, String postText, List<String> hashtags, byte[] imageData) {
|
||||
validateToken(creds);
|
||||
try {
|
||||
String fullText = buildPostText(postText, hashtags);
|
||||
String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);
|
||||
|
||||
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("access_token", creds.pageToken);
|
||||
@@ -157,7 +156,7 @@ public class FacebookPostingService {
|
||||
private String postWithVideo(PageCredentials creds, String postText, List<String> hashtags, byte[] videoData) {
|
||||
validateToken(creds);
|
||||
try {
|
||||
String fullText = buildPostText(postText, hashtags);
|
||||
String fullText = SocialPostTextFormatter.buildPostText(postText, hashtags);
|
||||
|
||||
log.info("[Facebook] Загружаю видео размером {} MB для страницы {}",
|
||||
String.format("%.1f", videoData.length / 1024.0 / 1024.0), creds.pageId);
|
||||
@@ -294,17 +293,6 @@ public class FacebookPostingService {
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPostText(String postText, List<String> hashtags) {
|
||||
StringBuilder sb = new StringBuilder(postText != null ? postText : "");
|
||||
if (hashtags != null && !hashtags.isEmpty()) {
|
||||
if (sb.length() > 0) sb.append("\n\n");
|
||||
sb.append(hashtags.stream()
|
||||
.map(t -> t.startsWith("#") ? t : "#" + t)
|
||||
.collect(Collectors.joining(" ")));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекаем короткий заголовок из текста поста (первые 100 символов до точки/переноса)
|
||||
*/
|
||||
@@ -345,4 +333,4 @@ public class FacebookPostingService {
|
||||
this.pageToken = pageToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class LinkedInPostingService {
|
||||
@@ -58,8 +57,8 @@ public class LinkedInPostingService {
|
||||
*/
|
||||
public String postToLinkedIn(String accessToken, String postText, List<String> hashtags) {
|
||||
try {
|
||||
// Формируем полный текст поста с хештегами
|
||||
String fullPostText = buildPostText(postText, hashtags);
|
||||
// Формируем полный текст поста с контактами и хештегами
|
||||
String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags);
|
||||
|
||||
// Получаем URN пользователя
|
||||
String personUrn = getPersonUrn(accessToken);
|
||||
@@ -99,8 +98,8 @@ public class LinkedInPostingService {
|
||||
public String postToLinkedInWithImage(String accessToken, String postText, List<String> hashtags,
|
||||
byte[] imageData, String imageContentType) {
|
||||
try {
|
||||
// Формируем полный текст поста с хештегами
|
||||
String fullPostText = buildPostText(postText, hashtags);
|
||||
// Формируем полный текст поста с контактами и хештегами
|
||||
String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags);
|
||||
|
||||
// Получаем URN пользователя
|
||||
String personUrn = getPersonUrn(accessToken);
|
||||
@@ -301,28 +300,6 @@ public class LinkedInPostingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Формирует полный текст поста с хештегами
|
||||
*/
|
||||
private String buildPostText(String postText, List<String> hashtags) {
|
||||
StringBuilder fullText = new StringBuilder(postText != null ? postText : "");
|
||||
|
||||
if (hashtags != null && !hashtags.isEmpty()) {
|
||||
if (fullText.length() > 0) {
|
||||
fullText.append("\n\n");
|
||||
}
|
||||
|
||||
// Добавляем хештеги, убеждаясь что они начинаются с #
|
||||
String hashtagsText = hashtags.stream()
|
||||
.map(tag -> tag.startsWith("#") ? tag : "#" + tag)
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
fullText.append(hashtagsText);
|
||||
}
|
||||
|
||||
return fullText.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, является ли ошибка истечением токена
|
||||
* LinkedIn возвращает HTTP 401 (Unauthorized) для истекших токенов
|
||||
@@ -369,4 +346,3 @@ public class LinkedInPostingService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class SocialPostTextFormatter {
|
||||
|
||||
static final String CONTACTS_BLOCK = "telegram - @konturaibot\nномер для звонка - 87273901485";
|
||||
|
||||
private static final String ELLIPSIS = "...";
|
||||
private static final String BLOCK_SEPARATOR = "\n\n";
|
||||
|
||||
private SocialPostTextFormatter() {
|
||||
}
|
||||
|
||||
static String buildPostText(String postText, List<String> hashtags) {
|
||||
return buildPostText(postText, hashtags, 0);
|
||||
}
|
||||
|
||||
static String buildPostText(String postText, List<String> hashtags, int maxLength) {
|
||||
String safePostText = postText != null ? postText : "";
|
||||
String hashtagsText = buildHashtagsText(hashtags);
|
||||
String fullText = joinBlocks(safePostText, CONTACTS_BLOCK, hashtagsText);
|
||||
|
||||
if (maxLength <= 0 || fullText.length() <= maxLength) {
|
||||
return fullText;
|
||||
}
|
||||
|
||||
return buildPostTextWithinLimit(safePostText, hashtagsText, maxLength);
|
||||
}
|
||||
|
||||
private static String buildPostTextWithinLimit(String postText, String hashtagsText, int maxLength) {
|
||||
String requiredSuffix = joinBlocks(CONTACTS_BLOCK, hashtagsText);
|
||||
if (requiredSuffix.length() >= maxLength) {
|
||||
return requiredSuffix.substring(0, maxLength);
|
||||
}
|
||||
|
||||
if (postText.isEmpty()) {
|
||||
return requiredSuffix;
|
||||
}
|
||||
|
||||
int availablePostLength = maxLength - requiredSuffix.length() - BLOCK_SEPARATOR.length();
|
||||
if (availablePostLength <= 0) {
|
||||
return requiredSuffix;
|
||||
}
|
||||
|
||||
return truncateWithEllipsis(postText, availablePostLength) + BLOCK_SEPARATOR + requiredSuffix;
|
||||
}
|
||||
|
||||
private static String buildHashtagsText(List<String> hashtags) {
|
||||
if (hashtags == null || hashtags.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return hashtags.stream()
|
||||
.filter(tag -> tag != null && !tag.isBlank())
|
||||
.map(String::strip)
|
||||
.map(tag -> tag.startsWith("#") ? tag : "#" + tag)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
private static String joinBlocks(String... blocks) {
|
||||
List<String> filledBlocks = new ArrayList<>();
|
||||
for (String block : blocks) {
|
||||
if (block != null && !block.isBlank()) {
|
||||
filledBlocks.add(block);
|
||||
}
|
||||
}
|
||||
return String.join(BLOCK_SEPARATOR, filledBlocks);
|
||||
}
|
||||
|
||||
private static String truncateWithEllipsis(String value, int maxLength) {
|
||||
if (value.length() <= maxLength) {
|
||||
return value;
|
||||
}
|
||||
if (maxLength <= ELLIPSIS.length()) {
|
||||
return value.substring(0, maxLength);
|
||||
}
|
||||
return value.substring(0, maxLength - ELLIPSIS.length()) + ELLIPSIS;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class TelegramPostingService {
|
||||
@@ -52,8 +51,8 @@ public class TelegramPostingService {
|
||||
// Парсим credentials
|
||||
TelegramCredentials creds = parseCredentials(credentials);
|
||||
|
||||
// Формируем полный текст поста с хештегами
|
||||
String fullPostText = buildPostText(postText, hashtags);
|
||||
// Формируем полный текст поста с контактами и хештегами
|
||||
String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags, 4096);
|
||||
|
||||
// Проверяем длину сообщения (Telegram limit: 4096 characters)
|
||||
if (fullPostText.length() > 4096) {
|
||||
@@ -102,8 +101,8 @@ public class TelegramPostingService {
|
||||
// Парсим credentials
|
||||
TelegramCredentials creds = parseCredentials(credentials);
|
||||
|
||||
// Формируем полный текст поста с хештегами
|
||||
String fullPostText = buildPostText(postText, hashtags);
|
||||
// Формируем полный текст поста с контактами и хештегами
|
||||
String fullPostText = SocialPostTextFormatter.buildPostText(postText, hashtags, 1024);
|
||||
|
||||
// Проверяем длину сообщения (Telegram limit: 1024 characters для caption)
|
||||
if (fullPostText.length() > 1024) {
|
||||
@@ -256,28 +255,6 @@ public class TelegramPostingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Формирует полный текст поста с хештегами
|
||||
*/
|
||||
private String buildPostText(String postText, List<String> hashtags) {
|
||||
StringBuilder fullText = new StringBuilder(postText != null ? postText : "");
|
||||
|
||||
if (hashtags != null && !hashtags.isEmpty()) {
|
||||
if (fullText.length() > 0) {
|
||||
fullText.append("\n\n");
|
||||
}
|
||||
|
||||
// Добавляем хештеги, убеждаясь что они начинаются с #
|
||||
String hashtagsText = hashtags.stream()
|
||||
.map(tag -> tag.startsWith("#") ? tag : "#" + tag)
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
fullText.append(hashtagsText);
|
||||
}
|
||||
|
||||
return fullText.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, является ли ошибка истечением токена
|
||||
* Telegram возвращает HTTP 401 (Unauthorized) для недействительных токенов
|
||||
@@ -357,4 +334,3 @@ public class TelegramPostingService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SocialPostTextFormatterTest {
|
||||
|
||||
@Test
|
||||
void buildPostTextShouldAlwaysAppendContactsBeforeHashtags() {
|
||||
String text = SocialPostTextFormatter.buildPostText("Новый продукт", List.of("ai", "#crm"));
|
||||
|
||||
assertEquals(
|
||||
"Новый продукт\n\n" +
|
||||
"telegram - @konturaibot\n" +
|
||||
"номер для звонка - 87273901485\n\n" +
|
||||
"#ai #crm",
|
||||
text);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildPostTextShouldIncludeContactsWhenPostTextAndHashtagsAreMissing() {
|
||||
String text = SocialPostTextFormatter.buildPostText(null, List.of());
|
||||
|
||||
assertEquals(SocialPostTextFormatter.CONTACTS_BLOCK, text);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildPostTextShouldPreserveContactsWithinTelegramLimit() {
|
||||
String longPostText = "A".repeat(1_100);
|
||||
|
||||
String text = SocialPostTextFormatter.buildPostText(longPostText, List.of("marketing"), 120);
|
||||
|
||||
assertTrue(text.length() <= 120);
|
||||
assertTrue(text.contains("telegram - @konturaibot"));
|
||||
assertTrue(text.contains("номер для звонка - 87273901485"));
|
||||
assertTrue(text.endsWith("#marketing"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user