'commit'
This commit is contained in:
@@ -80,15 +80,20 @@ async def get_station_list(d, service, max_scrolls=MAX_SCROLLS):
|
||||
|
||||
if ad_type == "rabbit":
|
||||
logger.info(">>> 检测到顽固的兔子广告,执行强力双击策略...")
|
||||
# 使用 u2 的 double_click,它比两次 click + sleep 更快
|
||||
d.double_click(x, y, duration=0.05)
|
||||
|
||||
# 处理“误触背景卡片”的逻辑
|
||||
logger.info(">>> 考虑到可能误触背景卡片,等待 2s 后执行返回操作...")
|
||||
await asyncio.sleep(2.0)
|
||||
d.press("back")
|
||||
await asyncio.sleep(1.0)
|
||||
logger.info(">>> 已尝试从误触页面返回。")
|
||||
logger.info(">>> 等待 3s 以确认是否进入了二级页面...")
|
||||
await asyncio.sleep(3.0)
|
||||
|
||||
# 爱特极特征元素检测 (列表页特征)
|
||||
is_list_page = d(text="筛选").exists or d(text="排序").exists or d(text="搜索").exists
|
||||
if not is_list_page:
|
||||
logger.info(">>> 检测到当前不在列表页,尝试执行返回操作...")
|
||||
d.press("back")
|
||||
await asyncio.sleep(1.5)
|
||||
else:
|
||||
logger.info(">>> 当前仍在列表页,无需返回。")
|
||||
else:
|
||||
d.click(x, y)
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
@@ -4,7 +4,8 @@ import logging
|
||||
import os
|
||||
import time
|
||||
import uiautomator2 as u2
|
||||
from Apps.AiTeJiYiChong.Kit import click_image_template
|
||||
from Apps.AiTeJiYiChong.Kit import click_image_template, take_screenshot
|
||||
from Config.Config import TEMP_IMAGE_DIR
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
|
||||
@@ -11,13 +11,16 @@ import cv2
|
||||
import uuid
|
||||
from openai import AsyncOpenAI, BadRequestError
|
||||
from Config.Config import (
|
||||
ALY_LLM_API_KEY, VL_MODEL_NAME, VL_MODEL_NAME_AD
|
||||
ALY_LLM_API_KEY, VL_MODEL_NAME, VL_MODEL_NAME_AD, TEMP_IMAGE_DIR
|
||||
)
|
||||
from Apps.AiTeJiYiChong.Config.Setting import (
|
||||
SAFE_EXCLUDE_RATIO, FALLBACK_WIDTH, FALLBACK_HEIGHT,
|
||||
BOTTOM_SAFE_EXCLUDE_RATIO
|
||||
)
|
||||
from Apps.AiTeJiYiChong import Kit
|
||||
from Apps.AiTeJiYiChong.Kit import (
|
||||
read_image, save_image
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -78,92 +81,6 @@ class ReadImageKit:
|
||||
"严格返回纯JSON格式。"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def detect_ad_popup(cls, image_path: str, device_info=None):
|
||||
"""
|
||||
使用 VL 模型检测是否存在弹窗广告,并返回关闭按钮坐标
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
return None
|
||||
|
||||
with open(image_path, "rb") as image_file:
|
||||
encoded_image = base64.b64encode(image_file.read()).decode("utf-8")
|
||||
img_input = {"url": f"data:image/jpeg;base64,{encoded_image}"}
|
||||
|
||||
# 构建提示词
|
||||
prompt = (
|
||||
"请仔细检查这张图片中是否存在**弹窗广告**或**悬浮广告**。\n"
|
||||
"广告可能有以下几种形式:\n"
|
||||
"1. **屏幕中央的大型弹窗广告**:通常遮挡了页面内容,内容多为优惠券、活动推广等。\n"
|
||||
"2. **'新电兔AI'专属悬浮广告**(高频):\n"
|
||||
" - **位置范围**:严格锁定在**屏幕左侧边缘**(水平坐标约 0-250),**垂直高度处于中下部**(垂直坐标约 600-900)。\n"
|
||||
" - **视觉特征**:一个卡通兔子头,或带有'新电兔AI'字样的气泡图标。\n"
|
||||
" - **关闭按钮**:务必寻找位于该图标**正下方**的一个**黑色圆形小按钮(内含白色X)**。注意:关闭按钮通常与图标本体分离,悬浮在图标下方约 20-50 像素处。请精确返回这个黑色圆形按钮的中心坐标。\n"
|
||||
"3. **底部的横幅广告**:带有明显的关闭按钮。\n\n"
|
||||
"如果发现了上述任何一种广告,请务必找到该广告的**关闭按钮**(通常是一个'X'符号,或者'关闭'字样,或者位于广告下方/右上角的圆圈叉号)。\n"
|
||||
"请返回该关闭按钮的**中心坐标**。\n\n"
|
||||
"请以纯 JSON 格式输出:\n"
|
||||
"{\n"
|
||||
" \"has_ad\": true/false,\n"
|
||||
" \"ad_type\": \"rabbit\", // \"rabbit\" (新电兔AI/兔子) | \"center\" (中央弹窗) | \"bottom\" (底部横幅) | \"other\"\n"
|
||||
" \"close_point\": [x, y] // 0-1000 归一化坐标\n"
|
||||
"}\n"
|
||||
"如果没有广告,请返回 `{\"has_ad\": false}`。\n"
|
||||
"只输出 JSON,不要包含任何其他文字。"
|
||||
)
|
||||
|
||||
try:
|
||||
resp = await cls._client.chat.completions.create(
|
||||
model=VL_MODEL_NAME, # 使用默认 VL 模型即可,或者使用 AD 专用
|
||||
timeout=30,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "image_url", "image_url": img_input},
|
||||
{"type": "text", "text": prompt},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
content = resp.choices[0].message.content.strip()
|
||||
raw_json = cls._extract_json(content)
|
||||
result = json.loads(raw_json)
|
||||
|
||||
if result.get("has_ad") and result.get("close_point"):
|
||||
if result.get("ad_type") == "rabbit":
|
||||
logger.info("大模型找到了兔子广告!")
|
||||
# 强制使用固定坐标 (94, 830) - 归一化 0-1000
|
||||
# 根据多次 CV 测试结果 (94, 828) 和 (93, 830) 的综合评估
|
||||
logger.info(">>> 采用强制固定坐标策略 (94, 830) 以确保点击准确性")
|
||||
norm_point = [94, 830]
|
||||
else:
|
||||
norm_point = result["close_point"]
|
||||
|
||||
# 转换坐标
|
||||
if len(norm_point) == 2:
|
||||
if not device_info:
|
||||
device_info = cls._FALLBACK_DEVICE_INFO
|
||||
|
||||
w = device_info.get("displayWidth", FALLBACK_WIDTH)
|
||||
h = device_info.get("displayHeight", FALLBACK_HEIGHT)
|
||||
|
||||
# 假设模型返回的是 0-1000 的坐标
|
||||
x = int(norm_point[0] / 1000 * w)
|
||||
y = int(norm_point[1] / 1000 * h)
|
||||
|
||||
return {
|
||||
"x": x,
|
||||
"y": y,
|
||||
"ad_type": result.get("ad_type", "unknown")
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to detect ad popup: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _extract_json(text: str) -> str:
|
||||
if not text:
|
||||
|
||||
Reference in New Issue
Block a user