Sou novata na área de programação e estou montando um telebot que se baseia no sistema gacha. Existe um comando nele chamado /matilha que mostra uma subcategoria em específico com dados de um banco sqlite. Estou tentando incluir paginação pra subcategorias com mais de 20 cards listados, mas não está dando certo. Como faço para montar a paginação com a condição de +20 cards listados por subcategoria e como deixo o código mais condensado? Porque como as funcionalidades foram sendo acrescentadas aos poucos, ele tá todo remendado/redundante e não estou gostando nem um pouco mas não tenho conhecimento pra arrumar sem acabar perdendo uma funcionalidade ou outra. # ----------------------------------------------- COMANDO MATILHA --------------------------------------------- #dicionario global page_data = {} user_selections = {} filtros_ativos = {} estados_filtros = {} #diminuição da sensibilidade da busca subcategoria_associacoes = { 'Stray Kids': ['stray kids', 'skz', '143'], '(G)I-DLE': ['gidle', '2018'], 'O Castelo Animado': ['castelo', 'castelo animado', 'o castelo animado', '24'], "That '70s Show": ['70s', "70's", 'that 70s show', "that 70's show", '70'], 'Princesa Mononoke': ['mononoke', 'princesa mononoke', '25'], 'Jujutsu Kaisen': ['jjk', 'jujutsu', 'jujutsu kaisen', '26'], 'TOMORROW X TOGETHER': ['TXT', 'txt', '101'], 'The Rose': ['The Rose', 'the rose', 'tr', '303'], 'Pentagon': ['Pentagon', 'pentagon', 'ptg', '305'], 'Monsta X': ['mx', '707'], 'Twice': ['TWICE', 'twice', '212'], 'Artistas Pop': ['pop', 'artistas pop', '10', 'apop'], 'WayV': ['wayv', '211'], 'A.C.E': ['ace', 'a.c.e', '304'] } #buscar subcategoria na tabela acima def buscar_subcategoria(nome): nome = nome.lower() for subcategoria, alias in subcategoria_associacoes.items(): if nome in alias or nome == subcategoria.lower(): return subcategoria return None #construção de texto com cards filtrados def construir_texto(cards): if not cards: return "Nenhum card encontrado com os filtros aplicados." lista_cards = [] for card_id, nome, raridade, quantidade_possuida in cards: icon = '' if raridade == 'Ouro' else '' if raridade == 'Prata' else '' quantidade_texto = f' ({quantidade_possuida}x)' if quantidade_possuida > 0 else ' ' lista_cards.append(f"{icon} {card_id}. {nome}{quantidade_texto}") return "\n".join(lista_cards) #atualizar mensagem matilha (com paginação) def atualizar_mensagem_matilha(message, user_id, subcategoria_id, subcategoria_nome, categoria_nome, subcategoria_imagem, cards, quantidade_total, mostrar_todos=False, pagina_atual=1, limite_por_pagina=20): try: #filtra os cards lista_filtrada = aplicar_filtros(cards, user_id, mostrar_todos) #calcula a paginação total_cards = len(lista_filtrada) ultima_pagina = (total_cards + limite_por_pagina - 1) // limite_por_pagina # Calcula o número de páginas inicio = (pagina_atual - 1) * limite_por_pagina fim = inicio + limite_por_pagina cards_paginados = lista_filtrada[inicio:fim] #construção de texto da pagina atual texto_atualizado = construir_texto(cards_paginados) markup = gerar_markup_filtros(subcategoria_id, user_id, pagina_atual, ultima_pagina) #legenda caption = ( f"A matilha ouviu seu chamado e se fez presente!\n\n" f"{subcategoria_id}. {subcategoria_nome} — {categoria_nome}\n" f"{total_cards} lobos disponíveis, {quantidade_total} em sua matilha.\n" f"Página {pagina_atual}/{ultima_pagina}\n\n" f"{texto_atualizado}\n\n" f"| Para visualizar o card individualmente, utilize o comando /wolf [id]." ) #edita a mensagem para mostrar os filtros aplicados/pagina passada if 'message_id' in page_data.get(user_id, {}): bot.edit_message_caption( chat_id=message.chat.id, message_id=page_data[user_id]['message_id'], caption=caption, reply_markup=markup ) else: with open(subcategoria_imagem, 'rb') as photo: sent_message = bot.send_photo( chat_id=message.chat.id, photo=photo, caption=caption, reply_markup=markup ) page_data[user_id] = {'message_id': sent_message.message_id} except Exception as e: print(f"Erro ao atualizar a mensagem da matilha: {e}") #aplicar filtros aos cards def aplicar_filtros(cards, user_id, mostrar_todos): if mostrar_todos: return cards filtros = filtros_ativos.get(user_id, {}) #filtra os cards a partir do filtro ativo cards_filtrados = [] for card in cards: card_id, nome, raridade, quantidade_possuida = card possui = quantidade_possuida > 0 if (filtros.get('possuo') and not possui) or (filtros.get('naopossuo') and possui): continue #ignora o card se não corresponde ao filtro possuo if filtros.get('ouro') and raridade != 'Ouro': continue #ignora o card se não for ouro quando o filtro ouro tá ativo if filtros.get('prata') and raridade != 'Prata': continue #ignora o card se não for prata quando o filtro prata tá ativo if filtros.get('bronze') and raridade != 'Bronze': continue #ignora o card se não for bronze quando o filtro bronze tá ativo #filtra o card que passou no filtro cards_filtrados.append(card) return cards_filtrados #teclado de filtros e botao de paginação def gerar_markup_filtros(subcategoria_id, user_id, pagina_atual, ultima_pagina): markup = InlineKeyboardMarkup(row_width=5) #botoes de filtros markup.add( InlineKeyboardButton("", callback_data=f"filtro_possuo_{subcategoria_id}_{user_id}"), InlineKeyboardButton("", callback_data=f"filtro_naopossuo_{subcategoria_id}_{user_id}"), InlineKeyboardButton("", callback_data=f"filtro_ouro_{subcategoria_id}_{user_id}"), InlineKeyboardButton("", callback_data=f"filtro_prata_{subcategoria_id}_{user_id}"), InlineKeyboardButton("", callback_data=f"filtro_bronze_{subcategoria_id}_{user_id}") ) #paginacao botoes_paginacao = [] if pagina_atual > 1: botoes_paginacao.append(InlineKeyboardButton("⬅️ Anterior", callback_data=f"pagina_{pagina_atual - 1}_{subcategoria_id}_{user_id}")) if pagina_atual < ultima_pagina: botoes_paginacao.append(InlineKeyboardButton("Próximo ➡️", callback_data=f"pagina_{pagina_atual + 1}_{subcategoria_id}_{user_id}")) if botoes_paginacao: markup.add(*botoes_paginacao) return markup #recuperar cards com filtros aplicados def recuperar_cards(subcategoria_id, filtro, estado_filtro, user_id, limite=20): query = ''' SELECT cards.card_id, cards.nome, cards.raridade, COALESCE(inventario.quantidade, 0) AS quantidade_possuida FROM cards LEFT JOIN inventario ON cards.card_id = inventario.card_id AND inventario.user_id = ? WHERE cards.subcategoria_id = ? ''' parametros = [user_id, subcategoria_id] if filtro == "possuo" and estado_filtro: query += " AND inventario.quantidade > 0" elif filtro == "naopossuo" and estado_filtro: # Alterado para naopossuo query += " AND (inventario.quantidade IS NULL OR inventario.quantidade = 0)" elif filtro in ["ouro", "prata", "bronze"] and estado_filtro: query += " AND cards.raridade = ?" parametros.append(filtro.capitalize()) query += ''' ORDER BY CASE cards.raridade WHEN 'Ouro' THEN 1 WHEN 'Prata' THEN 2 WHEN 'Bronze' THEN 3 END, cards.card_id ASC LIMIT ? ''' parametros.append(limite) cursor.execute(query, parametros) return cursor.fetchall() #extrair o subcategoria_id do callback data def obter_subcategoria_id_do_callback(callback_data): partes = callback_data.split('_') if len(partes) >= 3: return partes[2] return None #processar filtros em uma thread separada def processar_filtro_em_thread(filtro, user_id, subcategoria_id, message_id): try: #chama a função de recuperação de cards com os filtros cards = recuperar_cards(subcategoria_id=subcategoria_id, filtro=filtro, estado_filtro=True, user_id=user_id) #texto atualizado da mensagem com os cards filtrados texto_atualizado = construir_texto(cards) #verifica se `texto_atualizado` não está vazio if texto_atualizado: #edita a mensagem original com o novo texto bot.edit_message_text(chat_id=user_id, message_id=message_id, text=texto_atualizado) else: #caso o texto estiver vazio, retorna uma mensagem apropriada bot.edit_message_text(chat_id=user_id, message_id=message_id, text="Nenhum card encontrado com os filtros aplicados.") except Exception as e: print(f"Erro ao processar o callback_filtros: {e}") #verificar se o conteúdo da mensagem foi realmente alterado def conteudo_modificado(caption_novo, caption_atual, markup_novo, markup_atual): #verifica se a legenda e a marcação são diferentes daquelas já aplicadas return caption_novo.strip() != caption_atual.strip() or markup_novo != markup_atual #callback para manipular botões de paginação @bot.callback_query_handler(func=lambda call: call.data.startswith('pagina_')) def callback_paginacao(call): try: user_id = call.from_user.id dados_callback = call.data.split('_') #extrai a página, subcategoria_id e user_id dos dados do callback pagina_atual = int(dados_callback[1]) subcategoria_id = dados_callback[2] #obter subcategoria e categoria cursor.execute("SELECT nome, categoria_id, imagem FROM subcategorias WHERE id = ?", (subcategoria_id,)) subcategoria = cursor.fetchone() if not subcategoria: print(f"Subcategoria {subcategoria_id} não encontrada.") return subcategoria_nome, categoria_id, subcategoria_imagem = subcategoria cursor.execute("SELECT nome FROM categorias WHERE id = ?", (categoria_id,)) categoria_nome = cursor.fetchone() categoria_nome = categoria_nome[0] if categoria_nome else "Categoria desconhecida" #obter todos os cards da subcategoria e aplicar filtros cursor.execute(''' SELECT cards.card_id, cards.nome, cards.raridade, COALESCE(inventario.quantidade, 0) AS quantidade_possuida FROM cards LEFT JOIN inventario ON cards.card_id = inventario.card_id AND inventario.user_id = ? WHERE cards.subcategoria_id = ? ORDER BY CASE cards.raridade WHEN 'Ouro' THEN 1 WHEN 'Prata' THEN 2 WHEN 'Bronze' THEN 3 END, cards.card_id ASC ''', (user_id, subcategoria_id)) cards = cursor.fetchall() #atualizar a mensagem com a nova página e filtros atualizar_mensagem_matilha( call.message, user_id, subcategoria_id, subcategoria_nome, categoria_nome, subcategoria_imagem, cards, quantidade_total=len(cards), mostrar_todos=False, pagina_atual=pagina_atual ) bot.answer_callback_query(call.id, f"Página {pagina_atual} carregada.") except Exception as e: print(f"Erro no callback de paginação: {e}") #CALLBACK FILTRO @bot.callback_query_handler(func=lambda call: call.data.startswith('filtro_')) def callback_filtros(call): try: bot.answer_callback_query(call.id, "Processando filtro...") user_id = call.from_user.id dados_callback = call.data.split('_') filtro_nome = dados_callback[1] #deve capturar 'possuo', 'naopossuo', 'ouro', 'prata' ou 'bronze' subcategoria_id = dados_callback[2] #identifica o subcategoria_id filtro_ativo = filtros_ativos.get(user_id, {}) #alterna o estado do filtro e verifica se é válido if filtro_nome in ["possuo", "naopossuo", "ouro", "prata", "bronze"]: #alterna o filtro (ativa/desativa) filtro_ativo[filtro_nome] = not filtro_ativo.get(filtro_nome, False) filtros_ativos[user_id] = filtro_ativo else: print(f"Filtro '{filtro_nome}' não reconhecido.") return #recuperar informações da subcategoria cursor.execute("SELECT nome, categoria_id, imagem FROM subcategorias WHERE id = ?", (subcategoria_id,)) subcategoria = cursor.fetchone() if not subcategoria: print(f"Subcategoria {subcategoria_id} não encontrada.") return subcategoria_nome, categoria_id, subcategoria_imagem = subcategoria #obter o nome da categoria cursor.execute("SELECT nome FROM categorias WHERE id = ?", (categoria_id,)) categoria_nome = cursor.fetchone() if categoria_nome: categoria_nome = categoria_nome[0] else: print(f"Categoria {categoria_id} não encontrada.") return #obter todos os cards da subcategoria cursor.execute(''' SELECT cards.card_id, cards.nome, cards.raridade, COALESCE(inventario.quantidade, 0) AS quantidade_possuida FROM cards LEFT JOIN inventario ON cards.card_id = inventario.card_id AND inventario.user_id = ? WHERE cards.subcategoria_id = ? ORDER BY CASE cards.raridade WHEN 'Ouro' THEN 1 WHEN 'Prata' THEN 2 WHEN 'Bronze' THEN 3 END, cards.card_id ASC ''', (user_id, subcategoria_id)) cards = cursor.fetchall() #aplicar filtros se estiverem ativos if filtro_ativo.get("possuo"): cards = [card for card in cards if card[3] > 0] # Filtra apenas cards que o usuário possui if filtro_ativo.get("naopossuo"): cards = [card for card in cards if card[3] == 0] # Filtra apenas cards que o usuário não possui if filtro_ativo.get("ouro"): cards = [card for card in cards if card[2] == "Ouro"] # Filtra apenas cards ouro if filtro_ativo.get("prata"): cards = [card for card in cards if card[2] == "Prata"] # Filtra apenas cards prata if filtro_ativo.get("bronze"): cards = [card for card in cards if card[2] == "Bronze"] # Filtra apenas cards bronze #preparar o texto da legenda com os detalhes atualizados if cards: quantidade_total = sum(quantidade for _, _, _, quantidade in cards) caption_novo = ( f"A matilha ouviu seu chamado e se fez presente!\n\n" f"{subcategoria_id}. {subcategoria_nome} — {categoria_nome}\n" f"{len(cards)} lobos disponíveis, {quantidade_total} em sua matilha.\n\n" f"{construir_texto(cards)}\n\n" f"| Para visualizar o card individualmente, utilize o comando /wolf [id]." ) else: caption_novo = ( f"{subcategoria_nome} — {categoria_nome}\n" "Nenhum card encontrado com os filtros aplicados." ) #obter o markup atual e gerar o novo markup markup_atual = call.message.reply_markup markup_novo = gerar_markup_filtros(subcategoria_id, user_id) #verificar se a legenda realmente mudou antes de editar if conteudo_modificado(caption_novo, call.message.caption, markup_novo, markup_atual): bot.edit_message_caption( chat_id=call.message.chat.id, message_id=call.message.message_id, caption=caption_novo, reply_markup=markup_novo ) else: bot.answer_callback_query(call.id, "Nenhuma mudança detectada. Filtro já aplicado.") except Exception as e: print(f"Erro ao processar o callback_filtros: {e}") #FUNÇÃO MATILHA - essa que é puxada quando o usuario manda o comando /matilha no bot @bot.message_handler(commands=['matilha']) def comando_matilha(message): try: #pega o nome da subcategoria a partir da mensagem nome_subcategoria = message.text.split(' ', 1)[1] if len(message.text.split()) > 1 else '' #busca a subcategoria usando as associações subcategoria_nome = buscar_subcategoria(nome_subcategoria) if not subcategoria_nome: bot.send_message(message.chat.id, "Subcategoria não encontrada.") return #buscando o id da subcategoria no banco de dados cursor.execute("SELECT id, categoria_id, imagem FROM subcategorias WHERE nome = ?", (subcategoria_nome,)) subcategoria = cursor.fetchone() if not subcategoria: bot.send_message(message.chat.id, "Subcategoria não encontrada.") return #se a subcategoria for encontrada, continue com o restante do código subcategoria_id, categoria_id, subcategoria_imagem = subcategoria #obter o nome da categoria cursor.execute("SELECT nome FROM categorias WHERE id = ?", (categoria_id,)) categoria_nome = cursor.fetchone() if categoria_nome: categoria_nome = categoria_nome[0] else: bot.send_message(message.chat.id, "Categoria não encontrada.") return #recuperar o user_id user_id = message.from_user.id #recuperar todos os cards da subcategoria cards = recuperar_cards(subcategoria_id, None, False, user_id) #construir texto inicial quantidade_total = sum(quantidade for _, _, _, quantidade in cards) caption = ( f"A matilha ouviu seu chamado e se fez presente!\n\n" f"{subcategoria_id}. {subcategoria_nome} — {categoria_nome}\n" f"{len(cards)} lobos disponíveis, {quantidade_total} em sua matilha.\n\n" f"{construir_texto(cards)}\n\n" f"| Para visualizar o card individualmente, utilize o comando /wolf [id]." ) #enviar a nova mensagem com os cards with open(subcategoria_imagem, 'rb') as photo: bot.send_photo( chat_id=message.chat.id, photo=photo, caption=caption, reply_markup=gerar_markup_filtros(subcategoria_id, user_id) ) except Exception as e: print(f"Erro ao processar o comando /matilha: {e}") bot.send_message(message.chat.id, "Ocorreu um erro ao processar seu comando.") Continue reading...