import logging import uiautomator2 as u2 import time import os class FacebookBot: def __init__(self, device_ip: str): logging.info(f"Đang kết nối tới thiết bị {device_ip} để chạy Facebook...") self.d = u2.connect(device_ip) logging.info("Kết nối thành công!") def push_image_to_gallery(self, local_image_path: str): if not local_image_path or not os.path.exists(local_image_path): logging.warning("Không có file ảnh cục bộ để đẩy lên điện thoại.") return False import uuid logging.info(f"Đang đẩy ảnh {local_image_path} vào điện thoại...") # Tạo tên file độc nhất để đảm bảo luôn hiện lên ĐẦU thư viện ảnh random_name = f"n8n_fb_{uuid.uuid4().hex[:8]}.jpg" remote_path = f"/sdcard/Pictures/{random_name}" # Xóa bớt các file tạm cũ trước khi đẩy file mới (giữ điện thoại sạch sẽ) self.d.shell("rm -f /sdcard/Pictures/n8n_fb_*.jpg") self.d.push(local_image_path, remote_path) # Gọi Media Scanner để Thư viện ảnh nhận diện ngay lập tức logging.info("Đang cập nhật thư viện ảnh...") self.d.shell(f'am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://{remote_path}') time.sleep(2) return True def post_comment(self, group_id: str, content: str, local_image_path: str = None, post_index: int = 1) -> dict: try: self.d.screen_on() # Đẩy ảnh vào điện thoại nếu có has_image = False if local_image_path: has_image = self.push_image_to_gallery(local_image_path) logging.info(f"Khởi động Facebook và vào Group ID: {group_id}...") # Dùng lệnh ADB shell để mở thẳng URL của Group (Ép mở bằng app Facebook) # Dùng https:// thay vì fb:// vì một số Group bị lỗi deep link "Không hiển thị" self.d.shell(f'am start -a android.intent.action.VIEW -d "https://www.facebook.com/groups/{group_id}" com.facebook.katana') time.sleep(8) # Đợi load giao diện group self.dump_ui_for_debug("fb_step1_open_group.xml") # Kiểm tra xem Group có bị lỗi "Không hiển thị" (Do sai ID, group chết, group kín) hay không err_msg1 = self.d(descriptionContains="Nội dung này không hiển thị") err_msg2 = self.d(descriptionContains="chỉ hiển thị với một số người") if err_msg1.exists or err_msg2.exists: logging.warning(f"Group {group_id} không tồn tại hoặc bạn không có quyền truy cập.") return {"status": "error", "message": "Group không tồn tại hoặc không có quyền truy cập."} # Quét nút Bình luận, nếu không thấy thì vuốt xuống (vì ảnh bìa group che mất) logging.info(f"Tìm nút Bình luận của bài viết thứ {post_index}...") comment_btn = None current_post_count = 1 for _ in range(30): # Tăng số lần vuốt tối đa lên để có thể cuộn qua nhiều bài # Sửa lỗi: Nút Thích có description là "bày tỏ cảm xúc về bình luận" nên Regex .*bình luận.* sẽ bắt nhầm nút Thích! # Do đó ta chỉ tìm đúng chữ Bình luận đứng đầu, hoặc chữ Comment. c_desc_vn = self.d(descriptionMatches="(?i)^bình luận$") c_text_vn = self.d(textMatches="(?i)^bình luận$") c_desc_en = self.d(descriptionMatches="(?i)^comment$") c_text_en = self.d(textMatches="(?i)^comment$") btn = None if c_desc_vn.exists: btn = c_desc_vn elif c_text_vn.exists: btn = c_text_vn elif c_desc_en.exists: btn = c_desc_en elif c_text_en.exists: btn = c_text_en if btn and btn.exists: if current_post_count == post_index: comment_btn = btn break else: logging.info(f"Đã thấy bài viết thứ {current_post_count}, đang bỏ qua để tìm bài {post_index}...") # Vuốt qua bài viết hiện tại bằng 1 cú vuốt dài (70% màn hình) self.d.swipe_ext("up", scale=0.7) time.sleep(2) current_post_count += 1 continue # Nếu không thấy nút nào, vuốt lên một chút để tìm tiếp self.d.swipe_ext("up", scale=0.5) time.sleep(1.5) self.dump_ui_for_debug("fb_step2_scrolling.xml") if comment_btn and comment_btn.exists: comment_btn[0].click() # Click vào bài đầu tiên tìm thấy self.dump_ui_for_debug("fb_step3_clicked_comment.xml") else: self.dump_ui_for_debug("error_fb_no_comment_btn.xml") return {"status": "error", "message": "Không tìm thấy nút Bình luận. Đã lưu dump UI."} time.sleep(4) # Tìm ô nhập liệu "Viết bình luận công khai..." logging.info("Nhập nội dung text...") # Facebook có thể dùng EditText hoặc một class khác tùy phiên bản edit_box = self.d(className="android.widget.EditText") if not edit_box.exists: # Fallback: Tìm ô chứa chữ "Viết bình luận" hoặc "Write a comment" edit_box = self.d(descriptionMatches="(?i).*viết bình luận.*") if not edit_box.exists: edit_box = self.d(textMatches="(?i).*viết bình luận.*") if edit_box.exists: edit_box.click() time.sleep(1) edit_box.set_text(content) self.dump_ui_for_debug("fb_step7_text_entered.xml") else: self.dump_ui_for_debug("error_fb_no_edit_box.xml") return {"status": "error", "message": "Không tìm thấy ô gõ chữ. Có thể bạn bị cấm comment ở Group này."} # Chọn ảnh nếu có if has_image: logging.info("Đang chọn ảnh đính kèm...") # Nút chọn ảnh thường có chữ Camera hoặc Ảnh/Video photo_btn = self.d(descriptionContains="ảnh") if not photo_btn.exists: photo_btn = self.d(descriptionContains="photo") if photo_btn.exists: photo_btn.click() time.sleep(3) self.dump_ui_for_debug("fb_step5_camera_opened.xml") # Facebook gallery mới dùng Button với description 'Ảnh, mục X...' gallery_images = self.d(descriptionContains="Ảnh, mục") if not gallery_images.exists: gallery_images = self.d(descriptionContains="Photo") if not gallery_images.exists: gallery_images = self.d(descriptionContains="Ảnh") if gallery_images.exists: # Bấm vào ảnh đầu tiên (ảnh vừa mới push vào điện thoại sẽ nằm ở đầu danh sách) gallery_images[0].click() time.sleep(1) self.dump_ui_for_debug("fb_step6_image_selected.xml") # Nút Xong / Done done_btn = self.d(textContains="Xong") if not done_btn.exists: done_btn = self.d(textContains="Done") if done_btn.exists: done_btn.click() time.sleep(1) else: # Nếu không có nút Xong, bấm Back để gập cái thư viện ảnh lại, vì nó đang che khuất nút Gửi! logging.info("Không có nút Xong, gập thư viện ảnh xuống...") self.d.press("back") time.sleep(1.5) # Tìm nút "Gửi" (Send) logging.info("Tìm nút Gửi...") send_btn = self.d(descriptionMatches="(?i)^gửi$") if not send_btn.exists: send_btn = self.d(descriptionMatches="(?i)^send$") if not send_btn.exists: send_btn = self.d(descriptionContains="Gửi") if send_btn.exists: send_btn.click() time.sleep(3) return {"status": "success", "message": "Đã comment thành công!"} else: self.dump_ui_for_debug("error_fb_no_send_btn.xml") return {"status": "error", "message": "Không tìm thấy nút Gửi."} except Exception as e: logging.error(f"Lỗi Facebook Bot: {str(e)}") return {"status": "error", "message": f"Lỗi không xác định: {str(e)}"} finally: logging.info("Thoát ra màn hình chính (Homescreen)...") self.d.press("home") def dump_ui_for_debug(self, filename="ui_dump.xml"): import os os.makedirs("xml_ui", exist_ok=True) filepath = os.path.join("xml_ui", filename) xml_content = self.d.dump_hierarchy() with open(filepath, "w", encoding="utf-8") as f: f.write(xml_content) logging.info(f"Đã lưu cấu trúc UI hiện tại ra file {filepath} để debug.")