fix(mcp): filter out chat room members from contact list

_load_contacts_from() loaded all rows from the contact table without
filtering, causing ~8000+ chat room member records (local_type=3) to
appear as contacts. This inflated the contact count and polluted search
results. Add WHERE local_type != 3 to exclude chat room members.
This commit is contained in:
wingIsCrazy
2026-05-19 03:19:11 +08:00
committed by ylytdeng
parent 87b0b419d6
commit 7ab90dcd20

View File

@@ -16,8 +16,8 @@ import xml.etree.ElementTree as ET
from Crypto.Cipher import AES from Crypto.Cipher import AES
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
import zstandard as zstd import zstandard as zstd
from config import _config_file_path, _DEFAULT from config import _config_file_path, _DEFAULT
from decode_image import ImageResolver from decode_image import ImageResolver
from key_utils import get_key_info, key_path_variants, strip_key_metadata from key_utils import get_key_info, key_path_variants, strip_key_metadata
# ============ 加密常量 ============ # ============ 加密常量 ============
@@ -30,17 +30,17 @@ WAL_HEADER_SZ = 32
WAL_FRAME_HEADER_SZ = 24 WAL_FRAME_HEADER_SZ = 24
# ============ 配置加载 ============ # ============ 配置加载 ============
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = _config_file_path() CONFIG_FILE = _config_file_path()
try: try:
with open(CONFIG_FILE, encoding="utf-8") as f: with open(CONFIG_FILE, encoding="utf-8") as f:
_cfg = json.load(f) _cfg = json.load(f)
except FileNotFoundError: except FileNotFoundError:
_cfg = dict(_DEFAULT) _cfg = dict(_DEFAULT)
for _key in ("keys_file", "decrypted_dir"): for _key in ("keys_file", "decrypted_dir"):
if _key in _cfg and not os.path.isabs(_cfg[_key]): if _key in _cfg and not os.path.isabs(_cfg[_key]):
_cfg[_key] = os.path.join(os.path.dirname(CONFIG_FILE), _cfg[_key]) _cfg[_key] = os.path.join(os.path.dirname(CONFIG_FILE), _cfg[_key])
DB_DIR = _cfg["db_dir"] DB_DIR = _cfg["db_dir"]
KEYS_FILE = _cfg["keys_file"] KEYS_FILE = _cfg["keys_file"]
@@ -59,11 +59,11 @@ if not DECODED_IMAGE_DIR:
elif not os.path.isabs(DECODED_IMAGE_DIR): elif not os.path.isabs(DECODED_IMAGE_DIR):
DECODED_IMAGE_DIR = os.path.join(SCRIPT_DIR, DECODED_IMAGE_DIR) DECODED_IMAGE_DIR = os.path.join(SCRIPT_DIR, DECODED_IMAGE_DIR)
try: try:
with open(KEYS_FILE, encoding="utf-8") as f: with open(KEYS_FILE, encoding="utf-8") as f:
ALL_KEYS = strip_key_metadata(json.load(f)) ALL_KEYS = strip_key_metadata(json.load(f))
except FileNotFoundError: except FileNotFoundError:
ALL_KEYS = {} ALL_KEYS = {}
# ============ 解密函数 ============ # ============ 解密函数 ============
@@ -229,7 +229,7 @@ atexit.register(_cache.cleanup)
# ============ 联系人缓存 ============ # ============ 联系人缓存 ============
_contact_names = None # {username: display_name} _contact_names = None # {username: display_name}
_contact_full = None # [{username, nick_name, remark, alias, description, phone}] _contact_full = None # [{username, nick_name, remark, alias, description, phone}]
_contact_tags = None # {label_id: {name, sort_order, members: [{username, display_name}]}} _contact_tags = None # {label_id: {name, sort_order, members: [{username, display_name}]}}
_self_username = None _self_username = None
_contact_db_mtime = 0 # mtime of the decrypted contact.db when caches were last populated _contact_db_mtime = 0 # mtime of the decrypted contact.db when caches were last populated
@@ -247,55 +247,60 @@ _QUERY_LIMIT_MAX = 500
_HISTORY_QUERY_BATCH_SIZE = 500 _HISTORY_QUERY_BATCH_SIZE = 500
def _load_contacts_from(db_path): def _load_contacts_from(db_path):
names = {} names = {}
full = [] full = []
conn = sqlite3.connect(db_path) conn = sqlite3.connect(db_path)
try: try:
columns = { columns = {
row[1] for row in conn.execute("PRAGMA table_info(contact)").fetchall() row[1] for row in conn.execute("PRAGMA table_info(contact)").fetchall()
} }
optional_columns = { optional_columns = {
"alias": "", "alias": "",
"description": "", "description": "",
"phone": "", "phone": "",
"phone_number": "", "phone_number": "",
"mobile": "", "mobile": "",
"mobile_phone": "", "mobile_phone": "",
"telephone": "", "telephone": "",
} }
select_columns = ["username", "nick_name", "remark"] select_columns = ["username", "nick_name", "remark"]
select_columns.extend( select_columns.extend(
col for col in optional_columns col for col in optional_columns
if col in columns and col not in select_columns if col in columns and col not in select_columns
) )
rows = conn.execute( # 过滤群成员 (local_type=3, 每个群每个成员一条 → 数量爆炸 → 共 9416 假联系人)
"SELECT " + ", ".join(f"[{col}]" for col in select_columns) # 仅在 local_type 列存在时加 WHERE, 兼容老版本 schema (#117 fix)
+ " FROM contact" sql = (
).fetchall() "SELECT " + ", ".join(f"[{col}]" for col in select_columns)
for r in rows: + " FROM contact"
data = dict(zip(select_columns, r)) )
uname = data.get("username") if "local_type" in columns:
nick = data.get("nick_name") sql += " WHERE local_type != 3"
remark = data.get("remark") rows = conn.execute(sql).fetchall()
display = remark if remark else nick if nick else uname for r in rows:
names[uname] = display data = dict(zip(select_columns, r))
phone = "" uname = data.get("username")
for col in ("phone", "phone_number", "mobile", "mobile_phone", "telephone"): nick = data.get("nick_name")
if data.get(col): remark = data.get("remark")
phone = data.get(col) or "" display = remark if remark else nick if nick else uname
break names[uname] = display
full.append({ phone = ""
'username': uname, for col in ("phone", "phone_number", "mobile", "mobile_phone", "telephone"):
'nick_name': nick or '', if data.get(col):
'remark': remark or '', phone = data.get(col) or ""
'alias': data.get("alias") or '', break
'description': data.get("description") or '', full.append({
'phone': phone, 'username': uname,
}) 'nick_name': nick or '',
finally: 'remark': remark or '',
conn.close() 'alias': data.get("alias") or '',
return names, full 'description': data.get("description") or '',
'phone': phone,
})
finally:
conn.close()
return names, full
def _get_contact_db_path(): def _get_contact_db_path():
@@ -344,23 +349,23 @@ def get_contact_names():
return {} return {}
def get_contact_full(): def get_contact_full():
get_contact_names() get_contact_names()
return _contact_full or [] return _contact_full or []
def get_contact_tag_names_by_username(): def get_contact_tag_names_by_username():
tags = _load_contact_tags() tags = _load_contact_tags()
by_username = {} by_username = {}
for tag in tags.values(): for tag in tags.values():
name = tag.get('name') or '' name = tag.get('name') or ''
if not name: if not name:
continue continue
for member in tag.get('members', []): for member in tag.get('members', []):
username = member.get('username') username = member.get('username')
if username: if username:
by_username.setdefault(username, []).append(name) by_username.setdefault(username, []).append(name)
return by_username return by_username
def _extract_pb_field_30(data): def _extract_pb_field_30(data):