poke / main.py
misukisu's picture
Update main.py
87c3d03 verified
Raw
History Blame Contribute Delete
11.8 kB
import os
import asyncio
import logging
from typing import List, Dict, Any, Optional
import gradio as gr
from telethon import TelegramClient, events
from telethon.sessions import StringSession
# Setup Logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger = logging.getLogger("poke-client")
# ==========================================
# Configurations
# ==========================================
TELEGRAM_API_ID = 33348237
TELEGRAM_API_HASH = "0845b005d5e30b49b828e157fbaa86bf"
TARGET_BOT_USERNAME = "@interaction_poke_bot"
SESSION_FILE_PATH = "/tmp/telethon_session.txt"
MEDIA_DIR = "/tmp/poke_media"
os.makedirs(MEDIA_DIR, exist_ok=True)
# ==========================================
# Realtime Poke Client Manager
# ==========================================
class PokeClientManager:
def __init__(self):
self.client: Optional[TelegramClient] = None
self.session_string = os.getenv("TELEGRAM_SESSION_STRING", "")
self.is_connected = False
self.user_info = "Yhdistetään..."
self.target_entity = None
self.target_id = None
self.messages: List[Dict[str, Any]] = []
self.msg_id_map: Dict[int, int] = {}
self.lock = asyncio.Lock()
async def ensure_connected(self):
"""Varmistaa, että Telegram-yhteys toimii aina oikeassa Gradio event loopissa."""
current_loop = asyncio.get_running_loop()
# Jos loop on vaihtunut tai yhteys katkennut, alustetaan uudelleen
if self.client is not None:
client_loop = getattr(self.client, "_loop", None)
if client_loop != current_loop or not self.client.is_connected():
logger.info("Loop vaihtunut tai yhteys katkennut, alustetaan uudelleen...")
try:
await self.client.disconnect()
except Exception:
pass
self.client = None
self.is_connected = False
if not self.is_connected:
await self.start()
async def start(self):
session_to_use = self.session_string
if not session_to_use and os.path.exists(SESSION_FILE_PATH):
try:
with open(SESSION_FILE_PATH, "r") as f:
session_to_use = f.read().strip()
except Exception as e:
logger.warning(f"Ei voitu lukea session-tiedostoa: {e}")
if not session_to_use:
self.user_info = "Puuttuu TELEGRAM_SESSION_STRING"
return
try:
# Luodaan asiakas suoraan aktiiviseen event loopiin
self.client = TelegramClient(
StringSession(session_to_use),
TELEGRAM_API_ID,
TELEGRAM_API_HASH,
flood_sleep_threshold=24
)
await self.client.connect()
if await self.client.is_user_authorized():
me = await self.client.get_me()
self.user_info = f"@{me.username}" if getattr(me, "username", None) else f"{me.first_name} ({me.id})"
self.is_connected = True
self.target_entity = await self.client.get_input_entity(TARGET_BOT_USERNAME)
poke_full = await self.client.get_entity(TARGET_BOT_USERNAME)
self.target_id = poke_full.id
self._attach_event_listeners()
await self.sync_history(limit=30)
logger.info(f"Yhdistetty Telegramiin käyttäjänä {self.user_info}")
else:
self.user_info = "Istunto vanhentunut"
except Exception as e:
logger.exception(f"Yhdistysvirhe: {e}")
self.is_connected = False
self.user_info = f"Virhe: {e}"
def _attach_event_listeners(self):
@self.client.on(events.NewMessage(chats=self.target_entity))
async def handle_new_message(event):
try:
if event.sender_id == self.target_id:
await self._record_incoming_message(event.message)
except Exception as e:
logger.error(f"Virhe saapuvassa viestissä: {e}")
@self.client.on(events.MessageEdited(chats=self.target_entity))
async def handle_edited_message(event):
try:
if event.sender_id == self.target_id:
await self._update_edited_message(event.message)
except Exception as e:
logger.error(f"Virhe muokatussa viestissä: {e}")
async def _record_incoming_message(self, message):
media_path = None
if message.media:
try:
media_path = await self.client.download_media(message, file=MEDIA_DIR)
except Exception as e:
logger.warning(f"Kuvan lataus epäonnistui: {e}")
text = message.text or ""
if media_path:
self.messages.append({"role": "assistant", "content": {"path": media_path}})
if text:
self.messages.append({"role": "assistant", "content": text})
self.msg_id_map[message.id] = len(self.messages) - 1
async def _update_edited_message(self, message):
if message.id in self.msg_id_map:
idx = self.msg_id_map[message.id]
if idx < len(self.messages):
self.messages[idx]["content"] = message.text or ""
else:
await self._record_incoming_message(message)
async def sync_history(self, limit: int = 30):
if not self.client or not self.is_connected:
return
try:
self.messages.clear()
self.msg_id_map.clear()
tg_messages = await self.client.get_messages(self.target_entity, limit=limit)
for msg in reversed(tg_messages):
role = "user" if msg.out else "assistant"
media_path = None
if msg.media and hasattr(msg.media, 'photo'):
try:
media_path = await self.client.download_media(msg, file=MEDIA_DIR)
except Exception:
pass
if media_path:
self.messages.append({"role": role, "content": {"path": media_path}})
if msg.text:
self.messages.append({"role": role, "content": msg.text})
self.msg_id_map[msg.id] = len(self.messages) - 1
except Exception as e:
logger.warning(f"Historian haku epäonnistui: {e}")
async def send_message(self, text: str, files: Optional[List[str]] = None):
await self.ensure_connected()
if not self.is_connected or not self.client:
raise RuntimeError("Ei yhteyttä Telegramiin.")
text = (text or "").strip()
files = files or []
async with self.lock:
# Lähetetään kuvat
if files:
for file_path in files:
self.messages.append({"role": "user", "content": {"path": file_path}})
await self.client.send_file(
self.target_entity,
file=file_path,
caption=text if file_path == files[-1] else None
)
if text:
return
# Lähetetään teksti
if text:
self.messages.append({"role": "user", "content": text})
await self.client.send_message(self.target_entity, text)
manager = PokeClientManager()
# ==========================================
# Fullscreen Modern Gradio UI
# ==========================================
custom_css = """
/* Poistetaan ylimääräiset reunat ja marginaalit */
.gradio-container {
padding: 6px !important;
max-width: 100% !important;
height: 100dvh !important;
display: flex !important;
flex-direction: column !important;
box-sizing: border-box !important;
}
/* Tehdään chat-osiosta koko näytön korkuinen ja joustava */
#poke_chatbot {
flex-grow: 1 !important;
height: calc(100dvh - 145px) !important;
min-height: 400px !important;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
/* Piilotetaan Chatbot-otsikkolaatikko kokonaan */
.chatbot-header, .label-wrap {
display: none !important;
}
/* Viestikuplien asettelu */
.message-row {
padding: 4px 0 !important;
}
/* Pääpalkin tiivis muotoilu */
.header-bar {
padding: 4px 8px !important;
margin-bottom: 4px !important;
}
"""
custom_theme = gr.themes.Soft(
primary_hue="cyan",
secondary_hue="slate",
neutral_hue="slate"
).set(
body_background_fill="*neutral_950",
body_text_color="*neutral_100",
block_background_fill="*neutral_900",
block_border_width="0px",
input_background_fill="*neutral_800",
)
with gr.Blocks(title="Poke Live Client", fill_height=True, css=custom_css) as demo:
# Kompakti yläpalkki
with gr.Row(elem_classes=["header-bar"]):
with gr.Column(scale=8):
gr.Markdown(f"⚡ **Poke Client** (`{TARGET_BOT_USERNAME}`)")
with gr.Column(scale=4):
status_indicator = gr.Markdown(value="🟢 **Online**" if manager.is_connected else "🔴 **Yhdistetään...**")
# Koko näytön chat ilman robotti-ikonia tai laatikko-otsikkoa
chatbot = gr.Chatbot(
value=[],
elem_id="poke_chatbot",
show_label=False,
avatar_images=None,
scale=1,
)
# Viestikenttä ja lähetys
with gr.Row():
chat_input = gr.MultimodalTextbox(
placeholder="Kirjoita viesti tai liitä kuva...",
show_label=False,
file_types=["image"],
scale=9,
autofocus=True
)
send_btn = gr.Button("Lähetä", variant="primary", scale=1)
with gr.Row():
sync_btn = gr.Button("🔄 Päivitä historia", size="sm", variant="secondary")
clear_btn = gr.Button("🗑️ Tyhjennä näyttö", size="sm", variant="stop")
# 1 sekunnin automaattinen taustapäivitys
sync_timer = gr.Timer(value=1.0)
async def refresh_ui_messages():
await manager.ensure_connected()
status_text = f"🟢 **{manager.user_info}**" if manager.is_connected else f"🔴 **{manager.user_info}**"
return list(manager.messages), status_text
sync_timer.tick(
fn=refresh_ui_messages,
inputs=[],
outputs=[chatbot, status_indicator]
)
# Tapahtumakäsittelijät
async def handle_user_submit(msg_data):
if not msg_data:
return gr.MultimodalTextbox(value=None)
text = msg_data.get("text", "")
files = msg_data.get("files", [])
try:
await manager.send_message(text=text, files=files)
except Exception as e:
gr.Warning(f"Lähetys epäonnistui: {e}")
return gr.MultimodalTextbox(value=None)
chat_input.submit(
fn=handle_user_submit,
inputs=[chat_input],
outputs=[chat_input]
)
send_btn.click(
fn=handle_user_submit,
inputs=[chat_input],
outputs=[chat_input]
)
async def handle_resync():
await manager.sync_history(limit=30)
return gr.Info("Historia päivitetty!")
sync_btn.click(fn=handle_resync, inputs=[], outputs=[])
def handle_clear():
manager.messages.clear()
return []
clear_btn.click(fn=handle_clear, inputs=[], outputs=[chatbot])
# ==========================================
# Käynnistys
# ==========================================
if __name__ == "__main__":
demo.queue().launch(
server_name="0.0.0.0",
server_port=7860,
theme=custom_theme
)