ruff
This commit is contained in:
@@ -6,8 +6,8 @@ from pathlib import Path
|
|||||||
project_root = Path(__file__).parent.parent
|
project_root = Path(__file__).parent.parent
|
||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
from src.memory_graph.manager_singleton import get_unified_memory_manager
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.memory_graph.manager_singleton import get_unified_memory_manager
|
||||||
|
|
||||||
logger = get_logger("memory_transfer_check")
|
logger = get_logger("memory_transfer_check")
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ async def check_short_term_status():
|
|||||||
print(f"📊 当前记忆数量: {stats['total_memories']}/{stats['max_memories']}")
|
print(f"📊 当前记忆数量: {stats['total_memories']}/{stats['max_memories']}")
|
||||||
|
|
||||||
# 计算占用率
|
# 计算占用率
|
||||||
if stats['max_memories'] > 0:
|
if stats["max_memories"] > 0:
|
||||||
occupancy = stats['total_memories'] / stats['max_memories']
|
occupancy = stats["total_memories"] / stats["max_memories"]
|
||||||
print(f"📈 容量占用率: {occupancy:.1%}")
|
print(f"📈 容量占用率: {occupancy:.1%}")
|
||||||
|
|
||||||
# 根据占用率给出建议
|
# 根据占用率给出建议
|
||||||
@@ -103,7 +103,7 @@ async def check_auto_transfer_task():
|
|||||||
manager = get_unified_memory_manager()
|
manager = get_unified_memory_manager()
|
||||||
|
|
||||||
# 检查任务是否存在
|
# 检查任务是否存在
|
||||||
if not hasattr(manager, '_auto_transfer_task') or manager._auto_transfer_task is None:
|
if not hasattr(manager, "_auto_transfer_task") or manager._auto_transfer_task is None:
|
||||||
print("❌ 自动转移任务未创建!")
|
print("❌ 自动转移任务未创建!")
|
||||||
print("\n建议:调用 manager.initialize() 初始化系统")
|
print("\n建议:调用 manager.initialize() 初始化系统")
|
||||||
return False
|
return False
|
||||||
@@ -125,12 +125,12 @@ async def check_auto_transfer_task():
|
|||||||
print("✅ 自动转移任务正在运行")
|
print("✅ 自动转移任务正在运行")
|
||||||
|
|
||||||
# 检查转移缓存
|
# 检查转移缓存
|
||||||
if hasattr(manager, '_transfer_cache'):
|
if hasattr(manager, "_transfer_cache"):
|
||||||
cache_size = len(manager._transfer_cache) if manager._transfer_cache else 0
|
cache_size = len(manager._transfer_cache) if manager._transfer_cache else 0
|
||||||
print(f"📦 转移缓存: {cache_size} 条记忆")
|
print(f"📦 转移缓存: {cache_size} 条记忆")
|
||||||
|
|
||||||
# 检查上次转移时间
|
# 检查上次转移时间
|
||||||
if hasattr(manager, '_last_transfer_time'):
|
if hasattr(manager, "_last_transfer_time"):
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
last_time = manager._last_transfer_time
|
last_time = manager._last_transfer_time
|
||||||
if last_time:
|
if last_time:
|
||||||
@@ -169,7 +169,7 @@ async def manual_transfer_test():
|
|||||||
print("这将把当前符合条件的短期记忆转移到长期记忆")
|
print("这将把当前符合条件的短期记忆转移到长期记忆")
|
||||||
response = input("\n是否继续? (y/n): ").strip().lower()
|
response = input("\n是否继续? (y/n): ").strip().lower()
|
||||||
|
|
||||||
if response != 'y':
|
if response != "y":
|
||||||
print("❌ 已取消手动转移")
|
print("❌ 已取消手动转移")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -180,15 +180,15 @@ async def manual_transfer_test():
|
|||||||
result = await manager.manual_transfer()
|
result = await manager.manual_transfer()
|
||||||
|
|
||||||
print("\n✅ 转移完成!")
|
print("\n✅ 转移完成!")
|
||||||
print(f"\n转移结果:")
|
print("\n转移结果:")
|
||||||
print(f" 已处理: {result.get('processed_count', 0)} 条")
|
print(f" 已处理: {result.get('processed_count', 0)} 条")
|
||||||
print(f" 成功转移: {len(result.get('transferred_memory_ids', []))} 条")
|
print(f" 成功转移: {len(result.get('transferred_memory_ids', []))} 条")
|
||||||
print(f" 失败: {result.get('failed_count', 0)} 条")
|
print(f" 失败: {result.get('failed_count', 0)} 条")
|
||||||
print(f" 跳过: {result.get('skipped_count', 0)} 条")
|
print(f" 跳过: {result.get('skipped_count', 0)} 条")
|
||||||
|
|
||||||
if result.get('errors'):
|
if result.get("errors"):
|
||||||
print(f"\n错误信息:")
|
print("\n错误信息:")
|
||||||
for error in result['errors'][:3]: # 只显示前3个错误
|
for error in result["errors"][:3]: # 只显示前3个错误
|
||||||
print(f" - {error}")
|
print(f" - {error}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from pathlib import Path
|
|||||||
PROJECT_ROOT = Path(__file__).parent.parent
|
PROJECT_ROOT = Path(__file__).parent.parent
|
||||||
sys.path.insert(0, str(PROJECT_ROOT))
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
from src.config.config import global_config # noqa: E402
|
from src.config.config import global_config
|
||||||
from src.memory_graph.short_term_manager import ShortTermMemoryManager # noqa: E402
|
from src.memory_graph.short_term_manager import ShortTermMemoryManager
|
||||||
|
|
||||||
|
|
||||||
def resolve_data_dir() -> Path:
|
def resolve_data_dir() -> Path:
|
||||||
|
|||||||
@@ -12,17 +12,16 @@ from typing import Any, Optional, cast
|
|||||||
|
|
||||||
import json_repair
|
import json_repair
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from rich.traceback import install
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.chat.emoji_system.emoji_constants import EMOJI_DIR, EMOJI_REGISTERED_DIR, MAX_EMOJI_FOR_PROMPT
|
from src.chat.emoji_system.emoji_constants import EMOJI_DIR, EMOJI_REGISTERED_DIR, MAX_EMOJI_FOR_PROMPT
|
||||||
from src.chat.emoji_system.emoji_entities import MaiEmoji
|
from src.chat.emoji_system.emoji_entities import MaiEmoji
|
||||||
from src.chat.emoji_system.emoji_utils import (
|
from src.chat.emoji_system.emoji_utils import (
|
||||||
_emoji_objects_to_readable_list,
|
_emoji_objects_to_readable_list,
|
||||||
_to_emoji_objects,
|
|
||||||
_ensure_emoji_dir,
|
_ensure_emoji_dir,
|
||||||
clear_temp_emoji,
|
_to_emoji_objects,
|
||||||
clean_unused_emojis,
|
clean_unused_emojis,
|
||||||
|
clear_temp_emoji,
|
||||||
list_image_files,
|
list_image_files,
|
||||||
)
|
)
|
||||||
from src.chat.utils.utils_image import get_image_manager, image_path_to_base64
|
from src.chat.utils.utils_image import get_image_manager, image_path_to_base64
|
||||||
|
|||||||
@@ -578,8 +578,7 @@ class ExpressionLearner:
|
|||||||
logger.info(f"相同情景覆盖:'{same_situation_expr.situation}' 的表达从 '{same_situation_expr.style}' 更新为 '{style_val}'")
|
logger.info(f"相同情景覆盖:'{same_situation_expr.situation}' 的表达从 '{same_situation_expr.style}' 更新为 '{style_val}'")
|
||||||
# 更新映射
|
# 更新映射
|
||||||
old_key = (same_situation_expr.situation, same_situation_expr.style)
|
old_key = (same_situation_expr.situation, same_situation_expr.style)
|
||||||
if old_key in exact_match_map:
|
exact_match_map.pop(old_key, None)
|
||||||
del exact_match_map[old_key]
|
|
||||||
same_situation_expr.style = style_val
|
same_situation_expr.style = style_val
|
||||||
same_situation_expr.count = same_situation_expr.count + 1
|
same_situation_expr.count = same_situation_expr.count + 1
|
||||||
same_situation_expr.last_active_time = current_time
|
same_situation_expr.last_active_time = current_time
|
||||||
@@ -591,8 +590,7 @@ class ExpressionLearner:
|
|||||||
logger.info(f"相同表达覆盖:'{same_style_expr.style}' 的情景从 '{same_style_expr.situation}' 更新为 '{situation}'")
|
logger.info(f"相同表达覆盖:'{same_style_expr.style}' 的情景从 '{same_style_expr.situation}' 更新为 '{situation}'")
|
||||||
# 更新映射
|
# 更新映射
|
||||||
old_key = (same_style_expr.situation, same_style_expr.style)
|
old_key = (same_style_expr.situation, same_style_expr.style)
|
||||||
if old_key in exact_match_map:
|
exact_match_map.pop(old_key, None)
|
||||||
del exact_match_map[old_key]
|
|
||||||
same_style_expr.situation = situation
|
same_style_expr.situation = situation
|
||||||
same_style_expr.count = same_style_expr.count + 1
|
same_style_expr.count = same_style_expr.count + 1
|
||||||
same_style_expr.last_active_time = current_time
|
same_style_expr.last_active_time = current_time
|
||||||
@@ -627,8 +625,7 @@ class ExpressionLearner:
|
|||||||
await session.delete(expr)
|
await session.delete(expr)
|
||||||
# 从映射中移除
|
# 从映射中移除
|
||||||
key = (expr.situation, expr.style)
|
key = (expr.situation, expr.style)
|
||||||
if key in exact_match_map:
|
exact_match_map.pop(key, None)
|
||||||
del exact_match_map[key]
|
|
||||||
logger.debug(f"已删除 {len(all_current_exprs) - MAX_EXPRESSION_COUNT} 个低频表达方式")
|
logger.debug(f"已删除 {len(all_current_exprs) - MAX_EXPRESSION_COUNT} 个低频表达方式")
|
||||||
|
|
||||||
# 提交数据库更改
|
# 提交数据库更改
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# ruff: noqa: G004, BLE001
|
|
||||||
# pylint: disable=logging-fstring-interpolation,broad-except,unused-argument
|
# pylint: disable=logging-fstring-interpolation,broad-except,unused-argument
|
||||||
# pyright: reportOptionalMemberAccess=false
|
# pyright: reportOptionalMemberAccess=false
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ MaiZone(麦麦空间)- 重构版
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system import BasePlugin, ComponentInfo, register_plugin
|
from src.plugin_system import BasePlugin, ComponentInfo, register_plugin
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ class ContentService:
|
|||||||
- 运动风:"masterpiece, best quality, 1girl, sportswear, running in park, energetic, morning light, trees background, dynamic pose, healthy lifestyle"
|
- 运动风:"masterpiece, best quality, 1girl, sportswear, running in park, energetic, morning light, trees background, dynamic pose, healthy lifestyle"
|
||||||
- 咖啡馆:"masterpiece, best quality, 1girl, sitting in cozy cafe, holding coffee cup, warm lighting, wooden table, books beside, peaceful atmosphere"
|
- 咖啡馆:"masterpiece, best quality, 1girl, sitting in cozy cafe, holding coffee cup, warm lighting, wooden table, books beside, peaceful atmosphere"
|
||||||
"""
|
"""
|
||||||
output_format = '''{"text": "说说正文内容", "image": {"prompt": "详细的英文提示词(包含画质+主体+场景+氛围+光线+色彩)", "negative_prompt": "负面词", "include_character": true/false, "aspect_ratio": "方图/横图/竖图"}}'''
|
output_format = """{"text": "说说正文内容", "image": {"prompt": "详细的英文提示词(包含画质+主体+场景+氛围+光线+色彩)", "negative_prompt": "负面词", "include_character": true/false, "aspect_ratio": "方图/横图/竖图"}}"""
|
||||||
elif ai_image_enabled and provider == "siliconflow":
|
elif ai_image_enabled and provider == "siliconflow":
|
||||||
novelai_guide = """
|
novelai_guide = """
|
||||||
**配图说明:**
|
**配图说明:**
|
||||||
@@ -277,7 +277,7 @@ class ContentService:
|
|||||||
- "sunset over the calm ocean, golden hour, orange and purple sky, gentle waves, peaceful and serene mood, wide angle view"
|
- "sunset over the calm ocean, golden hour, orange and purple sky, gentle waves, peaceful and serene mood, wide angle view"
|
||||||
- "cherry blossoms in spring, soft pink petals falling, blue sky, sunlight filtering through branches, peaceful park scene, gentle breeze"
|
- "cherry blossoms in spring, soft pink petals falling, blue sky, sunlight filtering through branches, peaceful park scene, gentle breeze"
|
||||||
"""
|
"""
|
||||||
output_format = '''{"text": "说说正文内容", "image": {"prompt": "详细的英文描述(主体+场景+氛围+光线+细节)"}}'''
|
output_format = """{"text": "说说正文内容", "image": {"prompt": "详细的英文描述(主体+场景+氛围+光线+细节)"}}"""
|
||||||
|
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
{personality_desc}
|
{personality_desc}
|
||||||
|
|||||||
@@ -2,14 +2,11 @@
|
|||||||
NovelAI图片生成服务 - 空间插件专用
|
NovelAI图片生成服务 - 空间插件专用
|
||||||
独立实现,不依赖其他插件
|
独立实现,不依赖其他插件
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import io
|
||||||
import base64
|
|
||||||
import random
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
import io
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -60,11 +57,11 @@ class MaiZoneNovelAIService:
|
|||||||
async def generate_image_from_prompt_data(
|
async def generate_image_from_prompt_data(
|
||||||
self,
|
self,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
negative_prompt: Optional[str] = None,
|
negative_prompt: str | None = None,
|
||||||
include_character: bool = False,
|
include_character: bool = False,
|
||||||
width: int = 1024,
|
width: int = 1024,
|
||||||
height: int = 1024
|
height: int = 1024
|
||||||
) -> tuple[bool, Optional[Path], str]:
|
) -> tuple[bool, Path | None, str]:
|
||||||
"""根据提示词生成图片
|
"""根据提示词生成图片
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -85,7 +82,7 @@ class MaiZoneNovelAIService:
|
|||||||
final_prompt = prompt
|
final_prompt = prompt
|
||||||
if include_character and self.character_prompt:
|
if include_character and self.character_prompt:
|
||||||
final_prompt = f"{self.character_prompt}, {prompt}"
|
final_prompt = f"{self.character_prompt}, {prompt}"
|
||||||
logger.info(f"包含角色形象,添加角色提示词")
|
logger.info("包含角色形象,添加角色提示词")
|
||||||
|
|
||||||
# 合并负面提示词
|
# 合并负面提示词
|
||||||
final_negative = self.base_negative_prompt
|
final_negative = self.base_negative_prompt
|
||||||
@@ -95,7 +92,7 @@ class MaiZoneNovelAIService:
|
|||||||
else:
|
else:
|
||||||
final_negative = negative_prompt
|
final_negative = negative_prompt
|
||||||
|
|
||||||
logger.info(f"🎨 开始生成图片...")
|
logger.info("🎨 开始生成图片...")
|
||||||
logger.info(f" 尺寸: {width}x{height}")
|
logger.info(f" 尺寸: {width}x{height}")
|
||||||
logger.info(f" 正面提示词: {final_prompt[:100]}...")
|
logger.info(f" 正面提示词: {final_prompt[:100]}...")
|
||||||
logger.info(f" 负面提示词: {final_negative[:100]}...")
|
logger.info(f" 负面提示词: {final_negative[:100]}...")
|
||||||
@@ -118,7 +115,7 @@ class MaiZoneNovelAIService:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"生成图片时出错: {e}", exc_info=True)
|
logger.error(f"生成图片时出错: {e}", exc_info=True)
|
||||||
return False, None, f"生成失败: {str(e)}"
|
return False, None, f"生成失败: {e!s}"
|
||||||
|
|
||||||
def _build_payload(self, prompt: str, negative_prompt: str, width: int, height: int) -> dict:
|
def _build_payload(self, prompt: str, negative_prompt: str, width: int, height: int) -> dict:
|
||||||
"""构建NovelAI API请求payload"""
|
"""构建NovelAI API请求payload"""
|
||||||
@@ -197,7 +194,7 @@ class MaiZoneNovelAIService:
|
|||||||
|
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
async def _call_novelai_api(self, payload: dict) -> Optional[bytes]:
|
async def _call_novelai_api(self, payload: dict) -> bytes | None:
|
||||||
"""调用NovelAI API"""
|
"""调用NovelAI API"""
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
@@ -228,10 +225,10 @@ class MaiZoneNovelAIService:
|
|||||||
logger.info(f"收到响应数据: {len(img_data)} bytes")
|
logger.info(f"收到响应数据: {len(img_data)} bytes")
|
||||||
|
|
||||||
# 检查是否是ZIP文件
|
# 检查是否是ZIP文件
|
||||||
if img_data[:4] == b'PK\x03\x04':
|
if img_data[:4] == b"PK\x03\x04":
|
||||||
logger.info("检测到ZIP格式,解压中...")
|
logger.info("检测到ZIP格式,解压中...")
|
||||||
return self._extract_from_zip(img_data)
|
return self._extract_from_zip(img_data)
|
||||||
elif img_data[:4] == b'\x89PNG':
|
elif img_data[:4] == b"\x89PNG":
|
||||||
logger.info("检测到PNG格式")
|
logger.info("检测到PNG格式")
|
||||||
return img_data
|
return img_data
|
||||||
else:
|
else:
|
||||||
@@ -242,12 +239,12 @@ class MaiZoneNovelAIService:
|
|||||||
logger.error(f"API调用失败: {e}", exc_info=True)
|
logger.error(f"API调用失败: {e}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _extract_from_zip(self, zip_data: bytes) -> Optional[bytes]:
|
def _extract_from_zip(self, zip_data: bytes) -> bytes | None:
|
||||||
"""从ZIP中提取PNG"""
|
"""从ZIP中提取PNG"""
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
||||||
for filename in zf.namelist():
|
for filename in zf.namelist():
|
||||||
if filename.lower().endswith('.png'):
|
if filename.lower().endswith(".png"):
|
||||||
img_data = zf.read(filename)
|
img_data = zf.read(filename)
|
||||||
logger.info(f"从ZIP提取: {filename} ({len(img_data)} bytes)")
|
logger.info(f"从ZIP提取: {filename} ({len(img_data)} bytes)")
|
||||||
return img_data
|
return img_data
|
||||||
@@ -257,7 +254,7 @@ class MaiZoneNovelAIService:
|
|||||||
logger.error(f"解压ZIP失败: {e}")
|
logger.error(f"解压ZIP失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _save_image(self, image_data: bytes) -> Optional[Path]:
|
async def _save_image(self, image_data: bytes) -> Path | None:
|
||||||
"""保存图片到本地"""
|
"""保存图片到本地"""
|
||||||
try:
|
try:
|
||||||
filename = f"novelai_{uuid.uuid4().hex[:12]}.png"
|
filename = f"novelai_{uuid.uuid4().hex[:12]}.png"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ QQ空间服务模块
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
@@ -114,7 +113,7 @@ class QZoneService:
|
|||||||
}
|
}
|
||||||
width, height = size_map.get(aspect_ratio, (1024, 1024))
|
width, height = size_map.get(aspect_ratio, (1024, 1024))
|
||||||
|
|
||||||
logger.info(f"🎨 开始生成NovelAI配图...")
|
logger.info("🎨 开始生成NovelAI配图...")
|
||||||
success, img_path, msg = await novelai_service.generate_image_from_prompt_data(
|
success, img_path, msg = await novelai_service.generate_image_from_prompt_data(
|
||||||
prompt=image_info.get("prompt", ""),
|
prompt=image_info.get("prompt", ""),
|
||||||
negative_prompt=image_info.get("negative_prompt"),
|
negative_prompt=image_info.get("negative_prompt"),
|
||||||
@@ -125,7 +124,7 @@ class QZoneService:
|
|||||||
|
|
||||||
if success and img_path:
|
if success and img_path:
|
||||||
image_path = img_path
|
image_path = img_path
|
||||||
logger.info(f"✅ NovelAI配图生成成功")
|
logger.info("✅ NovelAI配图生成成功")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠️ NovelAI配图生成失败: {msg}")
|
logger.warning(f"⚠️ NovelAI配图生成失败: {msg}")
|
||||||
else:
|
else:
|
||||||
@@ -143,9 +142,9 @@ class QZoneService:
|
|||||||
)
|
)
|
||||||
if success and img_path:
|
if success and img_path:
|
||||||
image_path = img_path
|
image_path = img_path
|
||||||
logger.info(f"✅ 硅基流动配图生成成功")
|
logger.info("✅ 硅基流动配图生成成功")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"⚠️ 硅基流动配图生成失败")
|
logger.warning("⚠️ 硅基流动配图生成失败")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"硅基流动配图生成出错: {e}", exc_info=True)
|
logger.error(f"硅基流动配图生成出错: {e}", exc_info=True)
|
||||||
else:
|
else:
|
||||||
@@ -167,7 +166,7 @@ class QZoneService:
|
|||||||
try:
|
try:
|
||||||
with open(image_path, "rb") as f:
|
with open(image_path, "rb") as f:
|
||||||
images_bytes.append(f.read())
|
images_bytes.append(f.read())
|
||||||
logger.info(f"添加AI配图到说说")
|
logger.info("添加AI配图到说说")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"读取AI配图失败: {e}")
|
logger.error(f"读取AI配图失败: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user