This commit is contained in:
arys
2026-01-29 17:48:42 +05:00
parent 07fd55f33a
commit 7d0aef5a21
@@ -28,7 +28,15 @@ import java.util.stream.Collectors;
public class FacebookPostingService {
private static final Logger logger = LoggerFactory.getLogger(FacebookPostingService.class);
private static final String FACEBOOK_GRAPH_API_BASE = "https://graph.facebook.com/v19.0"; // Updated to v19.0
private static final String FACEBOOK_GRAPH_API_BASE = "https://graph.facebook.com/v19.0";
private static final String HARDCODED_PAGE_TOKEN = "EAAaocqgT3JoBQgZAlTa20336ZBM9ao5l8Cm6G4l4A495lzjgg5dFeVr722WkkKPa1KPs4ZAeeIq9bAGWHGbXmJnnIxMxLZCAbVcZBSnvKbFz24qFFEa9uyeIMuwV8RhbQPj5z2WmwuMifBZALxixzoxNCLDJGKuLhEPvZBnMJJlZC8dpZCH2haoVjjxKMgjm46jSDjfo7M0WVhvyIcs0Ki8Mu0XbitHEYBTnfd5AIqARg7ZAZChxlQ3oAng6fFCeHWvgIThk5spR4oJxrgh5qHudskZBEAZDZD";
// 2. Вставь сюда ID страницы (цифры)
private static final String HARDCODED_PAGE_ID = "EAAaocqgT3JoBQjjUQwZB0jsJ5ZCLjZCgLbTY4sqL0BBvR3II5AeYrrYaZBrAXLoEYOJEYL0tj9kkeZCZA5q3ZAiq7GxdoECSTfQKzQlSlzZBN5wfzOQOyoWXNpPIobbcWZAkcEeIDSZAiqdQxjZAvpCm0RJtvMaIruzmnSZCCbQvD3q4L3w8xjBXGTraKZCZApKPvtPjo2yEpWXuxVkXZBxFAN2AHz7rZAxHGG42qFuZCwrY0GKpxJadDl8kyOSDYiFvOi0gUhkPAW7jO4Gx9o3KoZCiLWtVxU";
// =================================================================================
private final WebClient webClient;
private final ObjectMapper objectMapper;
@@ -48,9 +56,22 @@ public class FacebookPostingService {
this.objectMapper = new ObjectMapper();
}
// === NEW METHOD FOR MESSAGING ===
public void sendPrivateMessage(String pageAccessToken, String recipientId, String messageText) {
/**
* Этот метод теперь просто возвращает хардкодные данные.
* Мы НЕ делаем запрос к /me/accounts, чтобы избежать ошибки 400.
*/
public PageCredentials getPageCredentials(String ignoredAccessToken) {
logger.info(">>> HARDCODED MODE: Возвращаем зашитые данные страницы {}", HARDCODED_PAGE_ID);
return new PageCredentials(HARDCODED_PAGE_ID, HARDCODED_PAGE_TOKEN);
}
// === ОТПРАВКА СООБЩЕНИЯ В ЛИЧКУ ===
public void sendPrivateMessage(String ignoredToken, String recipientId, String messageText) {
String tokenToUse = HARDCODED_PAGE_TOKEN;
try {
logger.info(">>> MSG SENDING: Отправляем сообщение юзеру {} с хардкодным токеном...", recipientId);
Map<String, Object> recipient = new HashMap<>();
recipient.put("id", recipientId);
@@ -65,7 +86,7 @@ public class FacebookPostingService {
String response = webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/me/messages")
.queryParam("access_token", pageAccessToken)
.queryParam("access_token", tokenToUse)
.build())
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body)
@@ -73,46 +94,54 @@ public class FacebookPostingService {
.bodyToMono(String.class)
.block(Duration.ofMillis(timeoutMs));
logger.info("Message sent successfully to user {}. Response: {}", recipientId, response);
logger.info("✅ SUCCESS: Сообщение отправлено! Response: {}", response);
} catch (WebClientResponseException e) {
logger.error("Failed to send message: {}", e.getResponseBodyAsString());
// Don't throw exception here to avoid failing the whole task if message fails
logger.error("❌ MSG ERROR: Code={}, Body={}", e.getStatusCode(), e.getResponseBodyAsString());
} catch (Exception e) {
logger.error("❌ MSG ERROR: ", e);
}
}
public String postToPage(String userAccessToken, String postText, List<String> hashtags) throws JsonProcessingException {
// === ПУБЛИКАЦИЯ ТЕКСТА ===
public String postToPage(String ignoredToken, String postText, List<String> hashtags) throws JsonProcessingException {
String tokenToUse = HARDCODED_PAGE_TOKEN;
String pageIdToUse = HARDCODED_PAGE_ID;
try {
String fullPostText = buildPostText(postText, hashtags);
PageCredentials creds = getPageCredentials(userAccessToken);
logger.info(">>> POSTING TEXT: Используем PageID={} Token={}...", pageIdToUse, tokenToUse.substring(0, 10));
String response = webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/{pageId}/feed")
.queryParam("access_token", creds.pageToken)
.queryParam("access_token", tokenToUse)
.queryParam("message", fullPostText)
.build(creds.pageId))
.build(pageIdToUse))
.retrieve()
.bodyToMono(String.class)
.block(Duration.ofMillis(timeoutMs));
JsonNode jsonNode = objectMapper.readTree(response);
String postId = jsonNode.get("id").asText();
logger.info("Posted to Page {}. ID: {}", creds.pageId, postId);
logger.info("✅ SUCCESS: Текстовый пост опубликован. ID: {}", postId);
return postId;
} catch (WebClientResponseException e) {
if (isTokenExpiredError(e)) throw parseFacebookError(e);
logger.error("Page API error: {}", e.getResponseBodyAsString());
throw new RuntimeException("Failed to post to Page: " + e.getMessage(), e);
handleFacebookError(e, "postToPage");
return null;
}
}
public String postToPageWithImage(String userAccessToken, String postText, List<String> hashtags,
byte[] imageData) {
// === ПУБЛИКАЦИЯ КАРТИНКИ ===
public String postToPageWithImage(String ignoredToken, String postText, List<String> hashtags, byte[] imageData) {
String tokenToUse = HARDCODED_PAGE_TOKEN;
String pageIdToUse = HARDCODED_PAGE_ID;
try {
logger.info(">>> POSTING IMAGE: Используем PageID={} Token={}...", pageIdToUse, tokenToUse.substring(0, 10));
String fullPostText = buildPostText(postText, hashtags);
PageCredentials creds = getPageCredentials(userAccessToken);
ByteArrayResource imageResource = new ByteArrayResource(imageData) {
@Override public String getFilename() { return "image.jpg"; }
@@ -121,10 +150,10 @@ public class FacebookPostingService {
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
formData.add("message", fullPostText);
formData.add("source", imageResource);
formData.add("access_token", creds.pageToken);
formData.add("access_token", tokenToUse);
String response = webClient.post()
.uri(uriBuilder -> uriBuilder.path("/{pageId}/photos").build(creds.pageId))
.uri(uriBuilder -> uriBuilder.path("/{pageId}/photos").build(pageIdToUse))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(formData))
.retrieve()
@@ -133,15 +162,14 @@ public class FacebookPostingService {
JsonNode jsonNode = objectMapper.readTree(response);
String postId = jsonNode.get("id").asText();
logger.info("Posted image to Page {}. ID: {}", creds.pageId, postId);
logger.info("✅ SUCCESS: Фото опубликовано. ID: {}", postId);
return postId;
} catch (WebClientResponseException e) {
if (isTokenExpiredError(e)) throw parseFacebookError(e);
logger.error("Page image API error: {}", e.getResponseBodyAsString());
throw new RuntimeException("Failed to post image to Page: " + e.getMessage(), e);
handleFacebookError(e, "postToPageWithImage");
return null;
} catch (Exception e) {
throw new RuntimeException("Failed to post image to Page", e);
throw new RuntimeException("Failed to post image to Page: " + e.getMessage(), e);
}
}
@@ -157,75 +185,23 @@ public class FacebookPostingService {
return fullText.toString();
}
private static class PageCredentials {
String pageId;
String pageToken;
// Класс сделали публичным, чтобы он был доступен в других пакетах если что,
// но в данном случае он используется как DTO
public static class PageCredentials {
public String pageId;
public String pageToken;
public PageCredentials(String pageId, String pageToken) {
this.pageId = pageId;
this.pageToken = pageToken;
}
}
private PageCredentials getPageCredentials(String accessToken) {
try {
// 1. Try to fetch accounts (This fails if token is already a Page Token)
String response = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/me/accounts")
.queryParam("access_token", accessToken)
.build())
.retrieve()
.bodyToMono(String.class)
.block(Duration.ofMillis(timeoutMs));
private void handleFacebookError(WebClientResponseException e, String context) {
String errorBody = e.getResponseBodyAsString();
logger.error("❌ FB API ERROR [{}]: Status={}, Body={}", context, e.getStatusCode(), errorBody);
JsonNode data = objectMapper.readTree(response).get("data");
if (data.isEmpty()) {
// If no pages found, try treating it as a Page Token directly
return getPageDetailsDirectly(accessToken);
}
JsonNode firstPage = data.get(0);
return new PageCredentials(
firstPage.get("id").asText(),
firstPage.get("access_token").asText());
} catch (WebClientResponseException e) {
// === FIX IS HERE ===
// If we get a 400 Bad Request, it means we cannot list accounts.
// This happens when the token is ALREADY a Page Access Token.
if (e.getStatusCode().value() == 400) {
logger.info("Got 400 on /me/accounts. Assuming token is a Page Token. Trying direct access.");
return getPageDetailsDirectly(accessToken);
}
if (isTokenExpiredError(e)) throw parseFacebookError(e);
throw new RuntimeException("Failed to get page credentials", e);
} catch (Exception e) {
throw new RuntimeException("Failed to get page credentials", e);
}
}
private PageCredentials getPageDetailsDirectly(String pageAccessToken) {
try {
// Verify the token works for /me and get the Page ID
String response = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/me")
.queryParam("access_token", pageAccessToken)
.queryParam("fields", "id,name")
.build())
.retrieve()
.bodyToMono(String.class)
.block(Duration.ofMillis(timeoutMs));
String pageId = objectMapper.readTree(response).get("id").asText();
logger.info("Successfully resolved Page Token for Page ID: {}", pageId);
return new PageCredentials(pageId, pageAccessToken);
} catch (Exception e) {
logger.error("Failed to verify Page Token: {}", e.getMessage());
throw new RuntimeException("Invalid token provided. Could not fetch Page ID.", e);
}
if (isTokenExpiredError(e)) throw parseFacebookError(e);
throw new RuntimeException("Facebook API Error (" + context + "): " + errorBody, e);
}
private boolean isTokenExpiredError(WebClientResponseException e) {