t1 / bot.py
iq7se2's picture
Update bot.py
fc28f55 verified
"""
ุจูˆุช ุชูŠู„ูŠุบุฑุงู… ู„ู„ู…ุงู†ุบุง ุงู„ุนุฑุจูŠุฉ
ูŠุฏุนู… ูƒู„ ุงู„ู…ูˆุงู‚ุน ุงู„ุนุฑุจูŠุฉ ุงู„ู…ูˆุฌูˆุฏุฉ ููŠ keiyoushi/extensions-source
"""
import os
import re
import asyncio
import logging
from typing import Optional
import requests
from bs4 import BeautifulSoup
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, \
CallbackQueryHandler, ContextTypes, ConversationHandler, filters
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุงู„ุฅุนุฏุงุฏุงุช
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
BOT_TOKEN = os.environ.get("BOT_TOKEN", "ุถุน_ุงู„ุชูˆูƒู†_ู‡ู†ุง")
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ู‚ุงุนุฏุฉ ุจูŠุงู†ุงุช ุงู„ู…ูˆุงู‚ุน
# ูƒู„ ู…ูˆู‚ุน ุนู†ุฏู‡ scraper ุฎุงุต
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
SITES = {
# โ”€โ”€ Madara (WordPress) โ”€โ”€ ู†ูุณ ุงู„ู†ุธุงู… ู„ุฃุบู„ุจ ุงู„ู…ูˆุงู‚ุน
"mangalek": {"name": "ู…ุงู†ุฌุง ู„ูŠูƒ", "url": "https://lek-manga.net", "type": "madara"},
"mangastarz": {"name": "ู…ุงู†ุฌุง ุณุชุงุฑุฒ", "url": "https://manga-starz.net", "type": "madara"},
"mangaspark": {"name": "ู…ุงู†ุฌุง ุณุจุงุฑูƒ", "url": "https://mangaspark.me", "type": "madara"},
"mangalink": {"name": "ู…ุงู†ุฌุง ู„ูŠู†ูƒ", "url": "https://manga-link.org", "type": "madara"},
"mangalionz": {"name": "ู…ุงู†ุฌุง ู„ูŠูˆู†ุฒ", "url": "https://mangalionz.com", "type": "madara"},
# โ”€โ”€ ู…ูˆุงู‚ุน ุจู†ุธุงู… ู…ุฎุชู„ู
"onma": {"name": "ุฃูˆู†ู…ุง", "url": "https://onma.me", "type": "onma"},
"olympus": {"name": "ุฃูˆู„ู…ุจุณ", "url": "https://olympustaff.com", "type": "olympus"},
}
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Scrapers โ€” ูƒู„ ู†ูˆุน ู…ูˆู‚ุน ู„ู‡ scraper
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def _get(url: str, **kwargs) -> Optional[BeautifulSoup]:
try:
r = requests.get(url, headers=HEADERS, timeout=15, **kwargs)
r.raise_for_status()
return BeautifulSoup(r.text, "html.parser")
except Exception as e:
log.error(f"GET {url} โ†’ {e}")
return None
def _post(url: str, data: dict) -> Optional[BeautifulSoup]:
try:
r = requests.post(url, headers=HEADERS, data=data, timeout=15)
r.raise_for_status()
return BeautifulSoup(r.text, "html.parser")
except Exception as e:
log.error(f"POST {url} โ†’ {e}")
return None
# โ”€โ”€ ู†ุธุงู… Madara (WordPress) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class MadaraScraper:
def __init__(self, base_url: str):
self.base = base_url.rstrip("/")
def search(self, query: str) -> list[dict]:
"""ุงู„ุจุญุซ ุนู† ู…ุงู†ุบุง"""
soup = _get(f"{self.base}/?s={query}&post_type=wp-manga")
if not soup:
return []
results = []
for item in soup.select(".c-tabs-item__content, .post-title"):
a = item.select_one("a")
if a:
results.append({
"title": a.get_text(strip=True),
"url": a["href"],
"slug": a["href"].rstrip("/").split("/")[-1],
})
return results[:8]
def get_chapters(self, manga_url: str) -> list[dict]:
"""ุฌู„ุจ ู‚ุงุฆู…ุฉ ุงู„ูุตูˆู„"""
soup = _get(manga_url)
if not soup:
return []
# ุงุณุชุฎุฑุฌ manga_id ู„ู„ู€ AJAX
manga_id = None
for el in soup.select("[id^='manga-chapters-holder']"):
manga_id = el.get("data-id")
break
chapters = []
if manga_id:
# ุทุฑูŠู‚ุฉ AJAX (ุฃุญุฏุซ)
ajax_soup = _post(
f"{self.base}/wp-admin/admin-ajax.php",
{"action": "manga_get_chapters", "manga": manga_id}
)
if ajax_soup:
soup = ajax_soup
for li in soup.select("li.wp-manga-chapter a, .chapter-li a"):
href = li.get("href", "")
if not href:
continue
num_match = re.search(r"chapter[- _](\d+\.?\d*)", href, re.I)
num = num_match.group(1) if num_match else li.get_text(strip=True)
chapters.append({
"name": li.get_text(strip=True) or f"ูุตู„ {num}",
"url": href,
"num": num,
})
return chapters
def get_images(self, chapter_url: str) -> list[str]:
"""ุฌู„ุจ ุตูˆุฑ ุงู„ูุตู„"""
soup = _get(chapter_url)
if not soup:
return []
imgs = soup.select(".page-break img, .reading-content img, img.wp-manga-chapter-img")
return [
(img.get("data-src") or img.get("data-lazy-src") or img.get("src", "")).strip()
for img in imgs
if img.get("data-src") or img.get("data-lazy-src") or img.get("src")
]
# โ”€โ”€ Olympus Staff โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class OlympusScraper:
def __init__(self):
self.base = "https://olympustaff.com"
def search(self, query: str) -> list[dict]:
soup = _get(f"{self.base}/series?search={query}")
if not soup:
return []
results = []
for a in soup.select("a[href*='/series/'][title]"):
slug = a["href"].rstrip("/").split("/")[-1]
results.append({
"title": a.get("title", slug),
"url": a["href"],
"slug": slug,
})
return results[:8]
def get_chapters(self, manga_url: str) -> list[dict]:
soup = _get(manga_url)
if not soup:
return []
chapters = []
for a in soup.select("a[href*='/series/']"):
href = a.get("href", "")
parts = href.rstrip("/").split("/")
if len(parts) >= 2:
last = parts[-1]
if re.match(r"^\d+\.?\d*$", last):
chapters.append({
"name": f"ูุตู„ {last}",
"url": href,
"num": last,
})
# ุฅุฒุงู„ุฉ ุงู„ุชูƒุฑุงุฑ
seen = set()
unique = []
for c in chapters:
if c["url"] not in seen:
seen.add(c["url"])
unique.append(c)
return unique
def get_images(self, chapter_url: str) -> list[str]:
soup = _get(chapter_url)
if not soup:
return []
return [
img["src"] for img in soup.select("img[src*='/uploads/']")
]
# โ”€โ”€ Onma โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class OnmaScraper:
def __init__(self):
self.base = "https://onma.me"
def search(self, query: str) -> list[dict]:
soup = _get(f"{self.base}/?s={query}")
if not soup:
return []
results = []
for a in soup.select("h3.post-title a, .manga_title a"):
results.append({
"title": a.get_text(strip=True),
"url": a["href"],
"slug": a["href"].rstrip("/").split("/")[-1],
})
return results[:8]
def get_chapters(self, manga_url: str) -> list[dict]:
# onma ูŠุณุชุฎุฏู… Madara ุฃูŠุถุงู‹
return MadaraScraper(self.base).get_chapters(manga_url)
def get_images(self, chapter_url: str) -> list[str]:
return MadaraScraper(self.base).get_images(chapter_url)
# โ”€โ”€ Factory: ูŠุฑุฌุน ุงู„ู€ scraper ุงู„ู…ู†ุงุณุจ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def get_scraper(site_key: str):
site = SITES.get(site_key)
if not site:
return None
t = site["type"]
if t == "madara":
return MadaraScraper(site["url"])
elif t == "olympus":
return OlympusScraper()
elif t == "onma":
return OnmaScraper()
return None
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุญุงู„ุงุช ุงู„ู…ุญุงุฏุซุฉ
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
SELECT_SITE, SELECT_MANGA, SELECT_CHAPTER = range(3)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ู…ุนุงู„ุฌุงุช ุงู„ุจูˆุช
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"๐ŸŽŒ *ู…ุฑุญุจุงู‹ ุจูƒ ููŠ ุจูˆุช ุงู„ู…ุงู†ุบุง ุงู„ุนุฑุจูŠุฉ!*\n\n"
"ุงู„ุฃูˆุงู…ุฑ:\n"
"๐Ÿ” /search โ€” ุงุจุญุซ ุนู† ู…ุงู†ุบุง\n"
"๐Ÿ“‹ /sites โ€” ู‚ุงุฆู…ุฉ ุงู„ู…ูˆุงู‚ุน ุงู„ู…ุฏุนูˆู…ุฉ\n"
"โ“ /help โ€” ุงู„ู…ุณุงุนุฏุฉ",
parse_mode="Markdown"
)
async def cmd_sites(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
text = "๐Ÿ“‹ *ุงู„ู…ูˆุงู‚ุน ุงู„ู…ุฏุนูˆู…ุฉ:*\n\n"
for key, site in SITES.items():
text += f"โ€ข `{key}` โ€” {site['name']} ({site['url']})\n"
await update.message.reply_text(text, parse_mode="Markdown")
async def cmd_search(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""ุงู„ุฎุทูˆุฉ 1: ุงุฎุชูŠุงุฑ ุงู„ู…ูˆู‚ุน"""
buttons = [
[InlineKeyboardButton(f"{s['name']}", callback_data=f"site:{k}")]
for k, s in SITES.items()
]
await update.message.reply_text(
"๐ŸŒ *ุงุฎุชุฑ ุงู„ู…ูˆู‚ุน:*",
reply_markup=InlineKeyboardMarkup(buttons),
parse_mode="Markdown"
)
return SELECT_SITE
async def cb_select_site(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""ุงู„ุฎุทูˆุฉ 1 โ†’ ุงุฎุชุงุฑ ุงู„ู…ูˆู‚ุน"""
query = update.callback_query
await query.answer()
site_key = query.data.split(":")[1]
ctx.user_data["site"] = site_key
site_name = SITES[site_key]["name"]
await query.edit_message_text(
f"โœ… ุงุฎุชุฑุช: *{site_name}*\n\n"
"๐Ÿ” ุงูƒุชุจ ุงุณู… ุงู„ู…ุงู†ุบุง ู„ู„ุจุญุซ:",
parse_mode="Markdown"
)
return SELECT_MANGA
async def handle_search_query(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""ุงู„ุฎุทูˆุฉ 2: ุงู„ุจุญุซ ูˆุนุฑุถ ุงู„ู†ุชุงุฆุฌ"""
site_key = ctx.user_data.get("site")
if not site_key:
await update.message.reply_text("โŒ ุงูƒุชุจ /search ุฃูˆู„")
return ConversationHandler.END
query = update.message.text
scraper = get_scraper(site_key)
if not scraper:
await update.message.reply_text("โŒ ู…ูˆู‚ุน ุบูŠุฑ ู…ุฏุนูˆู…")
return ConversationHandler.END
msg = await update.message.reply_text("โณ ุฌุงุฑูŠ ุงู„ุจุญุซ...")
results = scraper.search(query)
if not results:
await msg.edit_text("โŒ ู…ุง ู„ู‚ูŠุช ู†ุชุงุฆุฌุŒ ุฌุฑุจ ุงุณู… ุซุงู†ูŠ")
return ConversationHandler.END
# ุฎุฒู‘ู† ุงู„ู†ุชุงุฆุฌ ููŠ ุงู„ุฐุงูƒุฑุฉ
ctx.user_data["results"] = results
buttons = [
[InlineKeyboardButton(r["title"][:50], callback_data=f"manga:{i}")]
for i, r in enumerate(results)
]
await msg.edit_text(
f"๐Ÿ“š ุงู„ู†ุชุงุฆุฌ ({len(results)}):",
reply_markup=InlineKeyboardMarkup(buttons)
)
return SELECT_CHAPTER
async def cb_select_manga(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""ุงู„ุฎุทูˆุฉ 3: ุงุฎุชูŠุงุฑ ู…ุงู†ุบุง ูˆุนุฑุถ ุงู„ูุตูˆู„"""
query = update.callback_query
await query.answer()
idx = int(query.data.split(":")[1])
results = ctx.user_data.get("results", [])
site_key = ctx.user_data.get("site")
if idx >= len(results):
await query.edit_message_text("โŒ ุฎุทุฃ")
return ConversationHandler.END
manga = results[idx]
ctx.user_data["manga"] = manga
await query.edit_message_text(f"โณ ุฌุงุฑูŠ ุฌู„ุจ ูุตูˆู„: *{manga['title']}*...", parse_mode="Markdown")
scraper = get_scraper(site_key)
chapters = scraper.get_chapters(manga["url"])
if not chapters:
await query.edit_message_text("โŒ ู…ุง ู„ู‚ูŠุช ูุตูˆู„ุŒ ุงู„ู…ูˆู‚ุน ู‚ุฏ ูŠุญุชุงุฌ ุชุณุฌูŠู„ ุฏุฎูˆู„")
return ConversationHandler.END
ctx.user_data["chapters"] = chapters
# ุนุฑุถ ุฃูˆู„ 20 ูุตู„ (ุงู„ุฃุญุฏุซ)
show = chapters[:20]
btns = [
[InlineKeyboardButton(c["name"][:50], callback_data=f"chap:{i}")]
for i, c in enumerate(show)
]
btns += [[InlineKeyboardButton("๐Ÿ“‹ ุงู„ู…ุฒูŠุฏ", callback_data="chap:more")]]
await query.edit_message_text(
f"๐Ÿ“– *{manga['title']}*\n{len(chapters)} ูุตู„ โ€” ุงุฎุชุฑ ูุตู„:",
reply_markup=InlineKeyboardMarkup(btns),
parse_mode="Markdown"
)
return SELECT_CHAPTER
async def cb_select_chapter(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""ุงู„ุฎุทูˆุฉ 4: ุฅุฑุณุงู„ ุตูˆุฑ ุงู„ูุตู„"""
query = update.callback_query
await query.answer()
idx = query.data.split(":")[1]
if idx == "more":
# ุนุฑุถ ุจุงู‚ูŠ ุงู„ูุตูˆู„
chapters = ctx.user_data.get("chapters", [])
show = chapters[20:40]
btns = [
[InlineKeyboardButton(c["name"][:50], callback_data=f"chap:{i+20}")]
for i, c in enumerate(show)
]
await query.edit_message_text(
"๐Ÿ“‹ ุงู„ู…ุฒูŠุฏ ู…ู† ุงู„ูุตูˆู„:",
reply_markup=InlineKeyboardMarkup(btns)
)
return SELECT_CHAPTER
idx = int(idx)
chapters = ctx.user_data.get("chapters", [])
site_key = ctx.user_data.get("site")
chapter = chapters[idx]
await query.edit_message_text(f"โณ ุฌุงุฑูŠ ุชุญู…ูŠู„: *{chapter['name']}*...", parse_mode="Markdown")
scraper = get_scraper(site_key)
images = scraper.get_images(chapter["url"])
if not images:
await query.edit_message_text(
"โŒ ู…ุง ุฃู‚ุฏุฑ ุฃุฌูŠุจ ุงู„ุตูˆุฑ\n\n"
"ู…ู…ูƒู† ุงู„ูุตู„ ู…ุฏููˆุน ุฃูˆ ุงู„ู…ูˆู‚ุน ู…ุญุฌูˆุจ\n"
f"๐Ÿ”— [ุงูุชุญ ู…ุจุงุดุฑุฉ]({chapter['url']})",
parse_mode="Markdown"
)
return ConversationHandler.END
manga_title = ctx.user_data.get("manga", {}).get("title", "")
await query.edit_message_text(
f"๐Ÿ“ค ุฌุงุฑูŠ ุฅุฑุณุงู„ *{chapter['name']}*\n"
f"({len(images)} ุตูˆุฑุฉ)...",
parse_mode="Markdown"
)
# ุฅุฑุณุงู„ ุงู„ุตูˆุฑ โ€” ูƒู„ 10 ูƒู€ album
chat_id = update.effective_chat.id
sent = 0
for i in range(0, min(len(images), 50), 10):
batch = images[i:i+10]
try:
from telegram import InputMediaPhoto
media = [InputMediaPhoto(url) for url in batch]
await ctx.bot.send_media_group(chat_id, media)
sent += len(batch)
except Exception as e:
log.warning(f"ูุดู„ ุฅุฑุณุงู„ album: {e}")
# ุญุงูˆู„ ุฅุฑุณุงู„ ุตูˆุฑุฉ ุตูˆุฑุฉ
for url in batch:
try:
await ctx.bot.send_photo(chat_id, url)
sent += 1
except:
pass
await ctx.bot.send_message(
chat_id,
f"โœ… ุชู… ุฅุฑุณุงู„ {sent}/{len(images)} ุตูˆุฑุฉ\n"
f"๐Ÿ“– {manga_title} โ€” {chapter['name']}\n"
f"๐Ÿ”— [ุงู„ู…ุตุฏุฑ]({chapter['url']})",
parse_mode="Markdown"
)
return ConversationHandler.END
async def cmd_cancel(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("โŒ ุชู… ุงู„ุฅู„ุบุงุก")
return ConversationHandler.END
# โ”€โ”€ ุฃู…ุฑ ู…ุฎุชุตุฑ: /get olympus demonic-emperor 837 โ”€โ”€
async def cmd_get(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
args = ctx.args
if len(args) < 3:
await update.message.reply_text(
"โŒ ุงู„ุงุณุชุฎุฏุงู…:\n"
"`/get <site> <slug> <chapter>`\n\n"
"ู…ุซุงู„:\n"
"`/get olympus demonic-emperor 837`",
parse_mode="Markdown"
)
return
site_key, slug, chapter_num = args[0], args[1], args[2]
site = SITES.get(site_key)
if not site:
await update.message.reply_text(f"โŒ ู…ูˆู‚ุน ุบูŠุฑ ู…ุนุฑูˆู: `{site_key}`\n\nุงุณุชุฎุฏู… /sites ู„ุฑุคูŠุฉ ุงู„ู‚ุงุฆู…ุฉ", parse_mode="Markdown")
return
# ุจู†ุงุก ุฑุงุจุท ุงู„ูุตู„
if site_key == "olympus":
chapter_url = f"https://olympustaff.com/series/{slug}/{chapter_num}"
elif site["type"] == "madara":
chapter_url = f"{site['url']}/manga/{slug}/chapter-{chapter_num}"
else:
chapter_url = f"{site['url']}/manga/{slug}/chapter-{chapter_num}"
msg = await update.message.reply_text(f"โณ ุฌุงุฑูŠ ุฌู„ุจ ุงู„ูุตู„ {chapter_num} ู…ู† {site['name']}...")
scraper = get_scraper(site_key)
images = scraper.get_images(chapter_url)
if not images:
await msg.edit_text(
f"โŒ ู…ุง ุฃู‚ุฏุฑ ุฃุฌูŠุจ ุงู„ุตูˆุฑ\n"
f"๐Ÿ”— [ุงูุชุญ ู…ุจุงุดุฑุฉ]({chapter_url})",
parse_mode="Markdown"
)
return
await msg.edit_text(f"๐Ÿ“ค ุฅุฑุณุงู„ {len(images)} ุตูˆุฑุฉ...")
chat_id = update.effective_chat.id
sent = 0
for i in range(0, min(len(images), 50), 10):
batch = images[i:i+10]
try:
from telegram import InputMediaPhoto
await ctx.bot.send_media_group(chat_id, [InputMediaPhoto(u) for u in batch])
sent += len(batch)
except Exception as e:
for url in batch:
try:
await ctx.bot.send_photo(chat_id, url)
sent += 1
except:
pass
await ctx.bot.send_message(
chat_id,
f"โœ… ุชู…! {sent}/{len(images)} ุตูˆุฑุฉ\n"
f"๐Ÿ”— [ุงู„ู…ุตุฏุฑ]({chapter_url})",
parse_mode="Markdown"
)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุชุดุบูŠู„ ุงู„ุจูˆุช
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def build_app():
"""ุจู†ุงุก ุงู„ุชุทุจูŠู‚ ูู‚ุท โ€” ุจุฏูˆู† ุชุดุบูŠู„"""
application = ApplicationBuilder().token(BOT_TOKEN).build()
conv = ConversationHandler(
entry_points=[CommandHandler("search", cmd_search)],
states={
SELECT_SITE: [CallbackQueryHandler(cb_select_site, pattern=r"^site:")],
SELECT_MANGA: [
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_search_query),
],
SELECT_CHAPTER: [
CallbackQueryHandler(cb_select_manga, pattern=r"^manga:"),
CallbackQueryHandler(cb_select_chapter, pattern=r"^chap:"),
],
},
fallbacks=[CommandHandler("cancel", cmd_cancel)],
per_message=False,
)
application.add_handler(CommandHandler("start", cmd_start))
application.add_handler(CommandHandler("sites", cmd_sites))
application.add_handler(CommandHandler("get", cmd_get))
application.add_handler(conv)
return application
async def run_async():
"""ุชุดุบูŠู„ ุงู„ุจูˆุช ุจุฏูˆู† signal handlers โ€” ูŠุดุชุบู„ ู…ู† ุฃูŠ thread"""
application = build_app()
await application.initialize()
await application.start()
await application.updater.start_polling(
drop_pending_updates=True,
allowed_updates=Update.ALL_TYPES,
)
print("โœ… ุงู„ุจูˆุช ุดุบุงู„!")
# ุงู†ุชุธุฑ ุฅู„ู‰ ุงู„ุฃุจุฏ
await asyncio.Event().wait()
def main():
asyncio.run(run_async())
if __name__ == "__main__":
main()