diff --git a/src/main/java/kz/konturai/parser/service/FacebookPostingService.java b/src/main/java/kz/konturai/parser/service/FacebookPostingService.java index ac05413..ba73bbe 100644 --- a/src/main/java/kz/konturai/parser/service/FacebookPostingService.java +++ b/src/main/java/kz/konturai/parser/service/FacebookPostingService.java @@ -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 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 hashtags) throws JsonProcessingException { + // === ПУБЛИКАЦИЯ ТЕКСТА === + public String postToPage(String ignoredToken, String postText, List 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 hashtags, - byte[] imageData) { + // === ПУБЛИКАЦИЯ КАРТИНКИ === + public String postToPageWithImage(String ignoredToken, String postText, List 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 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) {