fix: make category callbacks id-based and tighten ownership checks
This commit is contained in:
57
SubMind.py
57
SubMind.py
@@ -241,25 +241,17 @@ def format_frequency(unit, value) -> str:
|
||||
return f"每 {value} {unit_map.get(unit, unit)}"
|
||||
|
||||
|
||||
CATEGORY_CB_PREFIX = "list_subs_in_category_"
|
||||
CATEGORY_CB_PREFIX = "list_subs_in_category_id_"
|
||||
EDITABLE_SUB_FIELDS = {'name', 'cost', 'currency', 'category', 'next_due', 'renewal_type', 'notes'}
|
||||
|
||||
|
||||
def _build_category_callback_data(category_name: str) -> str:
|
||||
"""Build callback_data within Telegram's 64-byte limit by falling back to a hash token."""
|
||||
candidate = f"{CATEGORY_CB_PREFIX}{category_name}"
|
||||
if len(candidate.encode('utf-8')) <= 64:
|
||||
return candidate
|
||||
token = abs(hash(category_name)) % 100000000
|
||||
return f"{CATEGORY_CB_PREFIX}h{token}"
|
||||
def _build_category_callback_data(category_id: int) -> str:
|
||||
return f"{CATEGORY_CB_PREFIX}{category_id}"
|
||||
|
||||
|
||||
def _parse_category_from_callback(data: str, context: CallbackContext) -> str | None:
|
||||
def _parse_category_id_from_callback(data: str) -> int | None:
|
||||
payload = data.replace(CATEGORY_CB_PREFIX, '', 1)
|
||||
if payload.startswith('h') and payload[1:].isdigit():
|
||||
mapping = context.user_data.get('category_cb_map', {})
|
||||
return mapping.get(payload)
|
||||
return payload
|
||||
return int(payload) if payload.isdigit() else None
|
||||
|
||||
|
||||
async def get_subs_list_keyboard(user_id: int, category_filter: str = None) -> InlineKeyboardMarkup:
|
||||
@@ -727,7 +719,7 @@ async def list_categories(update: Update, context: CallbackContext):
|
||||
user_id = update.effective_user.id
|
||||
with get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name FROM categories WHERE user_id = ? ORDER BY name", (user_id,))
|
||||
cursor.execute("SELECT id, name FROM categories WHERE user_id = ? ORDER BY name", (user_id,))
|
||||
categories = cursor.fetchall()
|
||||
if not categories:
|
||||
if update.callback_query:
|
||||
@@ -736,15 +728,10 @@ async def list_categories(update: Update, context: CallbackContext):
|
||||
await update.message.reply_text("您还没有任何分类。")
|
||||
return
|
||||
|
||||
context.user_data['category_cb_map'] = {}
|
||||
buttons = []
|
||||
for cat in categories:
|
||||
cat_name = cat[0]
|
||||
cb_data = _build_category_callback_data(cat_name)
|
||||
payload = cb_data.replace(CATEGORY_CB_PREFIX, '', 1)
|
||||
if payload.startswith('h') and payload[1:].isdigit():
|
||||
context.user_data['category_cb_map'][payload] = cat_name
|
||||
buttons.append(InlineKeyboardButton(cat_name, callback_data=cb_data))
|
||||
cat_id, cat_name = cat[0], cat[1]
|
||||
buttons.append(InlineKeyboardButton(cat_name, callback_data=_build_category_callback_data(cat_id)))
|
||||
|
||||
keyboard = [buttons[i:i + 2] for i in range(0, len(buttons), 2)]
|
||||
keyboard.append([InlineKeyboardButton("查看全部订阅", callback_data="list_all_subs")])
|
||||
@@ -795,11 +782,11 @@ async def show_subscription_view(update: Update, context: CallbackContext, sub_i
|
||||
keyboard_buttons.insert(0, [InlineKeyboardButton("✅ 续费", callback_data=f'renewmanual_{sub_id}')])
|
||||
if 'list_subs_in_category' in context.user_data:
|
||||
cat_filter = context.user_data['list_subs_in_category']
|
||||
back_cb = _build_category_callback_data(cat_filter)
|
||||
payload = back_cb.replace(CATEGORY_CB_PREFIX, '', 1)
|
||||
if payload.startswith('h') and payload[1:].isdigit():
|
||||
category_cb_map = context.user_data.setdefault('category_cb_map', {})
|
||||
category_cb_map[payload] = cat_filter
|
||||
category_id = context.user_data.get('list_subs_in_category_id')
|
||||
if category_id:
|
||||
back_cb = _build_category_callback_data(category_id)
|
||||
else:
|
||||
back_cb = 'list_categories'
|
||||
keyboard_buttons.append([InlineKeyboardButton("« 返回分类订阅", callback_data=back_cb)])
|
||||
else:
|
||||
keyboard_buttons.append([InlineKeyboardButton("« 返回全部订阅", callback_data='list_all_subs')])
|
||||
@@ -820,11 +807,23 @@ async def button_callback_handler(update: Update, context: CallbackContext):
|
||||
logger.debug(f"Received callback query: {data} from user {user_id}")
|
||||
|
||||
if data.startswith(CATEGORY_CB_PREFIX):
|
||||
category = _parse_category_from_callback(data, context)
|
||||
if not category:
|
||||
category_id = _parse_category_id_from_callback(data)
|
||||
if not category_id:
|
||||
await query.edit_message_text("错误:无效或已过期的分类,请重新选择。")
|
||||
return
|
||||
|
||||
with get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name FROM categories WHERE id = ? AND user_id = ?", (category_id, user_id))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
await query.edit_message_text("错误:分类不存在或无权限。")
|
||||
return
|
||||
|
||||
category = row['name']
|
||||
context.user_data['list_subs_in_category'] = category
|
||||
context.user_data['list_subs_in_category_id'] = category_id
|
||||
keyboard = await get_subs_list_keyboard(user_id, category_filter=category)
|
||||
msg_text = f"分类“{escape_markdown(category, version=2)}”下的订阅:"
|
||||
if not keyboard:
|
||||
@@ -834,10 +833,12 @@ async def button_callback_handler(update: Update, context: CallbackContext):
|
||||
return
|
||||
if data == 'list_categories':
|
||||
context.user_data.pop('list_subs_in_category', None)
|
||||
context.user_data.pop('list_subs_in_category_id', None)
|
||||
await list_categories(update, context)
|
||||
return
|
||||
if data == 'list_all_subs':
|
||||
context.user_data.pop('list_subs_in_category', None)
|
||||
context.user_data.pop('list_subs_in_category_id', None)
|
||||
keyboard = await get_subs_list_keyboard(user_id)
|
||||
if not keyboard:
|
||||
await query.edit_message_text("您还没有任何订阅。")
|
||||
|
||||
Reference in New Issue
Block a user