mirror of https://github.com/Mai-with-u/MaiBot.git
提取一些公共方法
parent
6db889580d
commit
6dc33e9e86
|
|
@ -54,7 +54,7 @@ def _install_stub_modules(monkeypatch):
|
|||
file_name: str = ""
|
||||
description: str | None = None
|
||||
emotion: list[str] | None = None
|
||||
emoji_hash: str | None = None
|
||||
file_hash: str | None = None
|
||||
is_deleted: bool = False
|
||||
query_count: int = 0
|
||||
register_time: object | None = None
|
||||
|
|
@ -831,7 +831,7 @@ def test_delete_emoji_file_missing_and_db_record_missing(monkeypatch):
|
|||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.full_path = _DummyPath()
|
||||
emoji.file_name = "missing.png"
|
||||
emoji.emoji_hash = "hash-missing"
|
||||
emoji.file_hash = "hash-missing"
|
||||
|
||||
result = manager.delete_emoji(emoji)
|
||||
|
||||
|
|
@ -859,7 +859,7 @@ def test_delete_emoji_file_delete_error(monkeypatch):
|
|||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.full_path = _DummyPath()
|
||||
emoji.file_name = "boom.png"
|
||||
emoji.emoji_hash = "hash-boom"
|
||||
emoji.file_hash = "hash-boom"
|
||||
|
||||
result = manager.delete_emoji(emoji)
|
||||
|
||||
|
|
@ -915,7 +915,7 @@ def test_delete_emoji_db_error_file_still_exists(monkeypatch):
|
|||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.full_path = _DummyPath()
|
||||
emoji.file_name = "keep.png"
|
||||
emoji.emoji_hash = "hash-keep"
|
||||
emoji.file_hash = "hash-keep"
|
||||
|
||||
result = manager.delete_emoji(emoji)
|
||||
|
||||
|
|
@ -988,7 +988,7 @@ def test_delete_emoji_success(monkeypatch):
|
|||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.full_path = _DummyPath()
|
||||
emoji.file_name = "ok.png"
|
||||
emoji.emoji_hash = "hash-ok"
|
||||
emoji.file_hash = "hash-ok"
|
||||
|
||||
result = manager.delete_emoji(emoji)
|
||||
|
||||
|
|
@ -1043,7 +1043,7 @@ def test_update_emoji_usage_success(monkeypatch):
|
|||
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash-ok"
|
||||
emoji.file_hash = "hash-ok"
|
||||
|
||||
result = manager.update_emoji_usage(emoji)
|
||||
|
||||
|
|
@ -1090,7 +1090,7 @@ def test_update_emoji_usage_missing_record(monkeypatch):
|
|||
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash-missing"
|
||||
emoji.file_hash = "hash-missing"
|
||||
|
||||
result = manager.update_emoji_usage(emoji)
|
||||
|
||||
|
|
@ -1129,7 +1129,7 @@ def test_update_emoji_usage_execute_error(monkeypatch):
|
|||
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash-execute"
|
||||
emoji.file_hash = "hash-execute"
|
||||
|
||||
result = manager.update_emoji_usage(emoji)
|
||||
|
||||
|
|
@ -1148,7 +1148,7 @@ def test_update_emoji_usage_get_db_session_error(monkeypatch):
|
|||
monkeypatch.setattr(emoji_manager_new, "get_db_session", _get_db_session)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash-session"
|
||||
emoji.file_hash = "hash-session"
|
||||
|
||||
result = manager.update_emoji_usage(emoji)
|
||||
|
||||
|
|
@ -1264,7 +1264,7 @@ async def test_build_emoji_description_calls_hash_and_sets_description(monkeypat
|
|||
)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = None
|
||||
emoji.file_hash = None
|
||||
emoji._format = "png"
|
||||
emoji.full_path = Path("/tmp/a.png")
|
||||
|
||||
|
|
@ -1292,7 +1292,7 @@ async def test_build_emoji_description_gif_conversion_error(monkeypatch):
|
|||
monkeypatch.setattr(emoji_manager_new.ImageUtils, "gif_2_static_image", staticmethod(_gif_to_static))
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash"
|
||||
emoji.file_hash = "hash"
|
||||
emoji._format = "gif"
|
||||
emoji.full_path = Path("/tmp/a.gif")
|
||||
|
||||
|
|
@ -1330,7 +1330,7 @@ async def test_build_emoji_description_content_filtration_reject(monkeypatch):
|
|||
)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash"
|
||||
emoji.file_hash = "hash"
|
||||
emoji._format = "png"
|
||||
emoji.full_path = Path("/tmp/a.png")
|
||||
|
||||
|
|
@ -1365,7 +1365,7 @@ async def test_build_emoji_description_content_filtration_pass(monkeypatch):
|
|||
)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash"
|
||||
emoji.file_hash = "hash"
|
||||
emoji._format = "png"
|
||||
emoji.full_path = Path("/tmp/a.png")
|
||||
|
||||
|
|
@ -1394,7 +1394,7 @@ async def test_build_emoji_description_vlm_exception_propagates(monkeypatch):
|
|||
)
|
||||
|
||||
emoji = emoji_manager_new.MaiEmoji()
|
||||
emoji.emoji_hash = "hash"
|
||||
emoji.file_hash = "hash"
|
||||
emoji._format = "png"
|
||||
emoji.full_path = Path("/tmp/a.png")
|
||||
|
||||
|
|
@ -1738,7 +1738,7 @@ async def test_register_emoji_by_filename_duplicate_hash(monkeypatch, tmp_path):
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-dup"
|
||||
self.file_hash = "hash-dup"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1765,7 +1765,7 @@ async def test_register_emoji_by_filename_build_description_failed(monkeypatch,
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-desc"
|
||||
self.file_hash = "hash-desc"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1793,7 +1793,7 @@ async def test_register_emoji_by_filename_build_emotion_failed(monkeypatch, tmp_
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-emo"
|
||||
self.file_hash = "hash-emo"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1827,7 +1827,7 @@ async def test_register_emoji_by_filename_capacity_replace_failed(monkeypatch, t
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-full"
|
||||
self.file_hash = "hash-full"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1866,7 +1866,7 @@ async def test_register_emoji_by_filename_capacity_replace_success(monkeypatch,
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-full-ok"
|
||||
self.file_hash = "hash-full-ok"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1904,7 +1904,7 @@ async def test_register_emoji_by_filename_register_db_failed(monkeypatch, tmp_pa
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-db-fail"
|
||||
self.file_hash = "hash-db-fail"
|
||||
self.full_path = file_path
|
||||
return True
|
||||
|
||||
|
|
@ -1939,7 +1939,7 @@ async def test_register_emoji_by_filename_register_db_success(monkeypatch, tmp_p
|
|||
|
||||
class _Emoji(emoji_manager_new.MaiEmoji):
|
||||
async def calculate_hash_format(self):
|
||||
self.emoji_hash = "hash-db-ok"
|
||||
self.file_hash = "hash-db-ok"
|
||||
self.full_path = file_path
|
||||
self.file_name = "db-ok.png"
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -144,12 +144,12 @@ class EmojiManager:
|
|||
# 删除数据库记录
|
||||
try:
|
||||
with get_db_session() as session:
|
||||
statement = select(Images).filter_by(image_hash=emoji.emoji_hash, image_type=ImageType.EMOJI).limit(1)
|
||||
statement = select(Images).filter_by(image_hash=emoji.file_hash, image_type=ImageType.EMOJI).limit(1)
|
||||
if image_record := session.exec(statement).first():
|
||||
session.delete(image_record)
|
||||
logger.info(f"[删除表情包] 成功删除数据库中的表情包记录: {emoji.emoji_hash}")
|
||||
logger.info(f"[删除表情包] 成功删除数据库中的表情包记录: {emoji.file_hash}")
|
||||
else:
|
||||
logger.warning(f"[删除表情包] 数据库中未找到表情包记录: {emoji.emoji_hash}")
|
||||
logger.warning(f"[删除表情包] 数据库中未找到表情包记录: {emoji.file_hash}")
|
||||
except Exception as e:
|
||||
logger.error(f"[删除表情包] 删除数据库记录时出错: {e}")
|
||||
# 如果数据库记录删除失败,但文件可能已删除,记录一个警告
|
||||
|
|
@ -170,14 +170,14 @@ class EmojiManager:
|
|||
"""
|
||||
try:
|
||||
with get_db_session() as session:
|
||||
statement = select(Images).filter_by(image_hash=emoji.emoji_hash, image_type=ImageType.EMOJI).limit(1)
|
||||
statement = select(Images).filter_by(image_hash=emoji.file_hash, image_type=ImageType.EMOJI).limit(1)
|
||||
if image_record := session.exec(statement).first():
|
||||
image_record.query_count += 1
|
||||
image_record.last_used_time = datetime.now()
|
||||
session.add(image_record)
|
||||
logger.info(f"[记录表情包使用] 成功记录表情包使用: {emoji.emoji_hash}")
|
||||
logger.info(f"[记录表情包使用] 成功记录表情包使用: {emoji.file_hash}")
|
||||
else:
|
||||
logger.error(f"[记录表情包使用] 未找到表情包记录: {emoji.emoji_hash}")
|
||||
logger.error(f"[记录表情包使用] 未找到表情包记录: {emoji.file_hash}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"[记录表情包使用] 记录使用时出错: {e}")
|
||||
|
|
@ -194,7 +194,7 @@ class EmojiManager:
|
|||
return (Optional[MaiEmoji]): 返回表情包对象,如果未找到则返回 None
|
||||
"""
|
||||
for emoji in self.emojis:
|
||||
if emoji.emoji_hash == emoji_hash and not emoji.is_deleted:
|
||||
if emoji.file_hash == emoji_hash and not emoji.is_deleted:
|
||||
return emoji
|
||||
logger.info(f"[获取表情包] 未找到哈希值为 {emoji_hash} 的表情包")
|
||||
return None
|
||||
|
|
@ -210,8 +210,8 @@ class EmojiManager:
|
|||
"""
|
||||
try:
|
||||
with get_db_session() as session:
|
||||
statement = select(Images).filter_by(image_hash=emoji_hash, image_type=ImageType.EMOJI)
|
||||
if image_record := session.execute(statement).scalars().first():
|
||||
statement = select(Images).filter_by(image_hash=emoji_hash, image_type=ImageType.EMOJI).limit(1)
|
||||
if image_record := session.exec(statement).first():
|
||||
return MaiEmoji.from_db_instance(image_record)
|
||||
logger.info(f"[数据库] 未找到哈希值为 {emoji_hash} 的表情包记录")
|
||||
return None
|
||||
|
|
@ -224,7 +224,7 @@ class EmojiManager:
|
|||
根据文本情感标签获取合适的表情包
|
||||
|
||||
Args:
|
||||
text_emotion (str): 文本的情感标签
|
||||
emotion_label (str): 文本的情感标签
|
||||
Returns:
|
||||
return (Optional[MaiEmoji]): 返回表情包对象,如果未找到则返回 None
|
||||
"""
|
||||
|
|
@ -320,7 +320,7 @@ class EmojiManager:
|
|||
Returns:
|
||||
return (Tuple[bool, MaiEmoji]): 返回是否成功构建描述,及表情包对象
|
||||
"""
|
||||
if not target_emoji.emoji_hash:
|
||||
if not target_emoji.file_hash:
|
||||
# Should not happen, but just in case
|
||||
await target_emoji.calculate_hash_format()
|
||||
|
||||
|
|
@ -502,7 +502,7 @@ class EmojiManager:
|
|||
return False
|
||||
file_full_path = target_emoji.full_path # 更新为可能修正后的路径
|
||||
# 2. 检查是否已经存在过
|
||||
if existing_emoji := self.get_emoji_by_hash(target_emoji.emoji_hash):
|
||||
if existing_emoji := self.get_emoji_by_hash(target_emoji.file_hash):
|
||||
logger.warning(f"[注册表情包] 表情包已存在,跳过注册: {existing_emoji.file_name}")
|
||||
return False
|
||||
# 3. 构建描述
|
||||
|
|
|
|||
|
|
@ -20,6 +20,20 @@ logger = get_logger("emoji")
|
|||
|
||||
|
||||
class BaseImageDataModel(BaseDatabaseDataModel[Images]):
|
||||
def __init__(self, full_path: str | Path, image_bytes: Optional[bytes] = None):
|
||||
if not full_path:
|
||||
# 创建时候即检测文件路径合法性
|
||||
raise ValueError("表情包路径不能为空")
|
||||
if Path(full_path).is_dir() or not Path(full_path).exists():
|
||||
raise FileNotFoundError(f"表情包路径无效: {full_path}")
|
||||
resolved_path = Path(full_path).absolute().resolve()
|
||||
self.full_path: Path = resolved_path
|
||||
self.dir_path: Path = resolved_path.parent.resolve()
|
||||
self.file_name: str = resolved_path.name
|
||||
self.file_hash: str = None # type: ignore
|
||||
|
||||
self.image_bytes: Optional[bytes] = image_bytes
|
||||
|
||||
def read_image_bytes(self, path: Path) -> bytes:
|
||||
"""
|
||||
同步读取图片文件的字节内容
|
||||
|
|
@ -65,55 +79,6 @@ class BaseImageDataModel(BaseDatabaseDataModel[Images]):
|
|||
logger.error(f"[获取图片格式] 读取图片格式时发生错误: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
class MaiEmoji(BaseImageDataModel):
|
||||
def __init__(self, full_path: str | Path):
|
||||
if not full_path:
|
||||
# 创建时候即检测文件路径合法性
|
||||
raise ValueError("表情包路径不能为空")
|
||||
if Path(full_path).is_dir() or not Path(full_path).exists():
|
||||
raise FileNotFoundError(f"表情包路径无效: {full_path}")
|
||||
resolved_path = Path(full_path).absolute().resolve()
|
||||
self.full_path: Path = resolved_path
|
||||
self.dir_path: Path = resolved_path.parent.resolve()
|
||||
self.file_name: str = resolved_path.name
|
||||
# self.embedding = []
|
||||
self.emoji_hash: str = None # type: ignore
|
||||
self.description = ""
|
||||
self.emotion: List[str] = []
|
||||
self.query_count = 0
|
||||
self.register_time: Optional[datetime] = None
|
||||
self.last_used_time: Optional[datetime] = None
|
||||
|
||||
# 私有属性
|
||||
self.is_deleted = False
|
||||
self._format: str = "" # 图片格式
|
||||
|
||||
@classmethod
|
||||
def from_db_instance(cls, db_record: Images):
|
||||
obj = cls(db_record.full_path)
|
||||
obj.emoji_hash = db_record.image_hash
|
||||
obj.description = db_record.description
|
||||
if db_record.emotion:
|
||||
obj.emotion = db_record.emotion.split(",")
|
||||
obj.query_count = db_record.query_count
|
||||
obj.last_used_time = db_record.last_used_time
|
||||
obj.register_time = db_record.register_time
|
||||
return obj
|
||||
|
||||
def to_db_instance(self) -> Images:
|
||||
emotion_str = ",".join(self.emotion) if self.emotion else None
|
||||
return Images(
|
||||
image_hash=self.emoji_hash,
|
||||
description=self.description,
|
||||
full_path=str(self.full_path),
|
||||
image_type=ImageType.EMOJI,
|
||||
emotion=emotion_str,
|
||||
query_count=self.query_count,
|
||||
last_used_time=self.last_used_time,
|
||||
register_time=self.register_time,
|
||||
)
|
||||
|
||||
async def calculate_hash_format(self) -> bool:
|
||||
"""
|
||||
异步计算表情包的哈希值和格式
|
||||
|
|
@ -121,13 +86,17 @@ class MaiEmoji(BaseImageDataModel):
|
|||
Returns:
|
||||
return (bool): 如果成功计算哈希值和格式则返回True,否则返回False
|
||||
"""
|
||||
logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
|
||||
|
||||
try:
|
||||
# 计算哈希值
|
||||
logger.debug(f"[初始化] 计算 {self.file_name} 的哈希值...")
|
||||
image_bytes = await asyncio.to_thread(self.read_image_bytes, self.full_path)
|
||||
self.emoji_hash = hashlib.sha256(image_bytes).hexdigest()
|
||||
logger.debug(f"[初始化] {self.file_name} 计算哈希值成功: {self.emoji_hash}")
|
||||
if not self.image_bytes:
|
||||
logger.debug(f"[初始化] 正在读取文件: {self.full_path}")
|
||||
image_bytes = await asyncio.to_thread(self.read_image_bytes, self.full_path)
|
||||
else:
|
||||
image_bytes = self.image_bytes
|
||||
self.file_hash = hashlib.sha256(image_bytes).hexdigest()
|
||||
logger.debug(f"[初始化] {self.file_name} 计算哈希值成功: {self.file_hash}")
|
||||
|
||||
# 用PIL读取图片格式
|
||||
logger.debug(f"[初始化] 读取 {self.file_name} 的图片格式...")
|
||||
|
|
@ -146,7 +115,47 @@ class MaiEmoji(BaseImageDataModel):
|
|||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"[初始化] 初始化表情包时发生错误: {e}")
|
||||
logger.error(f"[初始化] 初始化图片时发生错误: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
self.is_deleted = True
|
||||
return False
|
||||
|
||||
|
||||
class MaiEmoji(BaseImageDataModel):
|
||||
def __init__(self, full_path: str | Path, image_bytes: Optional[bytes] = None):
|
||||
# self.embedding = []
|
||||
self.description = ""
|
||||
self.emotion: List[str] = []
|
||||
self.query_count = 0
|
||||
self.register_time: Optional[datetime] = None
|
||||
self.last_used_time: Optional[datetime] = None
|
||||
|
||||
# 私有属性
|
||||
self.is_deleted = False
|
||||
self._format: str = "" # 图片格式
|
||||
super().__init__(full_path, image_bytes)
|
||||
|
||||
@classmethod
|
||||
def from_db_instance(cls, db_record: Images):
|
||||
obj = cls(db_record.full_path)
|
||||
obj.file_hash = db_record.image_hash
|
||||
obj.description = db_record.description
|
||||
if db_record.emotion:
|
||||
obj.emotion = db_record.emotion.split(",")
|
||||
obj.query_count = db_record.query_count
|
||||
obj.last_used_time = db_record.last_used_time
|
||||
obj.register_time = db_record.register_time
|
||||
return obj
|
||||
|
||||
def to_db_instance(self) -> Images:
|
||||
emotion_str = ",".join(self.emotion) if self.emotion else None
|
||||
return Images(
|
||||
image_hash=self.file_hash,
|
||||
description=self.description,
|
||||
full_path=str(self.full_path),
|
||||
image_type=ImageType.EMOJI,
|
||||
emotion=emotion_str,
|
||||
query_count=self.query_count,
|
||||
last_used_time=self.last_used_time,
|
||||
register_time=self.register_time,
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue