Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/locale/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "تم تعيين اللغة إلى العربية🇸🇦",
"start": "لقد قمت بتشغيل <b>No Watermark TikTok🤖</b>\n\nيدعم هذا البوت تنزيل:\n📹الفيديو، 🖼الصور و 🔈الصوت\nمن TikTok وInstagram <i><b>بدون علامة مائية</b></i>\n\nيمكنك أيضًا الاشتراك في قناتنا للحصول على آخر الأخبار حول حالة البوت والتحديثات والإعلانات!\n@ttgrab\n\n<b>أرسل رابط الفيديو للبدء</b>",
"maintenance": "⚠️ <b>صيانة البوت</b> ⚠️\n\nالبوت حاليًا تحت الصيانة وسيعود قريبًا.\nشكرًا لصبرك!\n\nتابع @ttgrab للحصول على التحديثات.",
"get_sound": "تحميل الصوت",
"get_sound": " \uD83C\uDFB5 تحميل الصوت",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>المصدر</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>غلاف الأغنية</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "The language is set to English🇺🇸",
"start": "You have launched <b>No Watermark TikTok🤖</b>\n\nThis bot supports download of:\n📹Video, 🖼Images and 🔈Audio\nfrom TikTok and Instagram <i><b>without watermark</b></i>\n\nYou can also subscribe to our channel to get the latest news about bot status, updates and news!\n@ttgrab\n\n<b>Send video link to get started</b>",
"maintenance": "⚠️ <b>Bot Maintenance</b> ⚠️\n\nThe bot is currently undergoing maintenance and will be back soon.\nThank you for your patience!\n\nFollow @ttgrab for updates.",
"get_sound": "Get Sound",
"get_sound": "\uD83C\uDFB5 Get Sound",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Source</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Song cover</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "भाषा हिन्दी🇮🇳 पर सेट है",
"start": "आपने <b>No Watermark TikTok🤖</b> शुरू किया है\n\nयह बॉट TikTok और Instagram से <i><b>बिना वॉटरमार्क</b></i>:\n📹वीडियो, 🖼छवियाँ और 🔈ऑडियो\nडाउनलोड करने का समर्थन करता है\n\nआप बॉट की स्थिति, अपडेट और खबरों के लिए हमारे चैनल को भी सब्सक्राइब कर सकते हैं!\n@ttgrab\n\n<b>शुरू करने के लिए वीडियो लिंक भेजें</b>",
"maintenance": "⚠️ <b>बॉट मेंटेनेंस</b> ⚠️\n\nबॉट अभी मेंटेनेंस में है और जल्द ही वापस आएगा।\nआपके धैर्य के लिए धन्यवाद!\n\nअपडेट के लिए @ttgrab फॉलो करें।",
"get_sound": "साउंड प्राप्त करें",
"get_sound": "\uD83C\uDFB5 साउंड प्राप्त करें",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>स्रोत</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>गाने का कवर</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "Bahasa disetel ke Bahasa Indonesia🇮🇩",
"start": "Kamu telah menjalankan <b>No Watermark TikTok🤖</b>\n\nBot ini mendukung unduhan:\n📹Video, 🖼Gambar, dan 🔈Audio\ndari TikTok dan Instagram <i><b>tanpa watermark</b></i>\n\nKamu juga bisa berlangganan channel kami untuk mendapatkan kabar terbaru tentang status bot, pembaruan, dan berita!\n@ttgrab\n\n<b>Kirim tautan video untuk memulai</b>",
"maintenance": "⚠️ <b>Pemeliharaan Bot</b> ⚠️\n\nBot sedang dalam pemeliharaan dan akan segera kembali.\nTerima kasih atas kesabaranmu!\n\nIkuti @ttgrab untuk pembaruan.",
"get_sound": "Unduh Audio",
"get_sound": "\uD83C\uDFB5 Unduh Audio",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Sumber</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Sampul lagu</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "Установлен язык Русский🇷🇺",
"start": "Вы запустили <b>No Watermark TikTok🤖</b>\n\nЭтот бот поддерживает загрузку:\n📹Видео, 🖼Изображений и 🔈Аудио\nс TikTok и Instagram <i><b>без водяного знака</b></i>\n\nВы также можете подписаться на наш канал, чтобы получать последние новости о статусе бота, обновлениях и новостях!\n@ttgrab\n\n<b>Отправьте ссылку на видео, чтобы начать</b>",
"maintenance": "⚠️ <b>Техническое обслуживание бота</b> ⚠️\n\nВ настоящее время бот находится на техническом обслуживании и скоро вернется.\nСпасибо за ваше терпение!\n\nСледите за обновлениями в @ttgrab.",
"get_sound": "Скачать звук",
"get_sound": "\uD83C\uDFB5 Скачать звук",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Оригинал</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Обложка</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/so.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "Luqadda waxaa loo dejiyey Soomaali🇸🇴",
"start": "Waxaad bilowday <b>No Watermark TikTok🤖</b>\n\nBot-kan wuxuu taageeraa soo dejinta:\n📹Fiidiyow, 🖼Sawirro iyo 🔈Cod\nTikTok iyo Instagram <i><b>aan watermark lahayn</b></i>\n\nWaxaad sidoo kale raaci kartaa kanaalkeenna si aad u hesho wararkii ugu dambeeyay ee xaaladda bot-ka, cusboonaysiinta iyo wararka!\n@ttgrab\n\n<b>Dir link-ga fiidiyowga si aad u bilowdo</b>",
"maintenance": "⚠️ <b>Dayactirka Bot-ka</b> ⚠️\n\nBot-ku hadda wuxuu ku jiraa dayactir wuxuuna soo laaban doonaa dhawaan.\nWaad ku mahadsan tahay dulqaadkaaga!\n\nRaac @ttgrab si aad u hesho warar cusub.",
"get_sound": "Hel Cod",
"get_sound": "\uD83C\uDFB5 Hel Cod",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Isha</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Daboolka heesta</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "Встановлено мову Українська🇺🇦",
"start": "Ви запустили <b>No Watermark TikTok🤖</b>\n\nЦей бот підтримує завантаження:\n📹Відео, 🖼Зображень та 🔈Аудіо\nз TikTok та Instagram <i><b>без водяного знака</b></i>\n\nВи також можете підписатися на наш канал, щоб отримувати останні новини про статус бота, оновлення та новини!\n@ttgrab\n\n<b>Надішліть посилання на відео, щоб почати</b>",
"maintenance": "⚠️ <b>Технічне обслуговування бота</b> ⚠️\n\nНаразі бот перебуває на технічному обслуговуванні і скоро повернеться.\nДякуємо за ваше терпіння!\n\nСлідкуйте за оновленнями в @ttgrab.",
"get_sound": "Завантажити звук",
"get_sound": "\uD83C\uDFB5 Завантажити звук",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Оригінал</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Обкладинка</a>\n\n<b>{0}</b>",
Expand Down
2 changes: 1 addition & 1 deletion data/locale/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lang": "Ngôn ngữ hiện tại: Tiếng Việt🇻🇳",
"start": "Bạn đã khởi chạy <b>No Watermark TikTok🤖</b>\n\nBot này hỗ trợ tải:\n📹Video, 🖼Hình ảnh và 🔈Âm thanh\ntừ TikTok và Instagram <i><b>không có watermark</b></i>\n\nBạn cũng có thể theo dõi kênh của chúng tôi để nhận tin mới nhất về trạng thái bot, cập nhật và thông báo!\n@ttgrab\n\n<b>Gửi liên kết video để bắt đầu</b>",
"maintenance": "⚠️ <b>Bảo trì bot</b> ⚠️\n\nBot hiện đang được bảo trì và sẽ sớm quay lại.\nCảm ơn bạn đã kiên nhẫn!\n\nTheo dõi @ttgrab để cập nhật.",
"get_sound": "Tải âm thanh",
"get_sound": "\uD83C\uDFB5 Tải âm thanh",
"bot_tag": "<a href='t.me/ttgrab_bot'>No Watermark TikTok</a>",
"result": "<a href='{1}'>Nguồn</a>\n\n<b>{0}</b>",
"result_song": "<a href='{1}'>Bìa bài hát</a>\n\n<b>{0}</b>",
Expand Down
5 changes: 3 additions & 2 deletions handlers/get_inline.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from media_types import send_video_result, get_error_message
from media_types.image_processing import ensure_native_format
from media_types.storage import upload_photo_to_storage
from media_types.ui import result_caption
from media_types.ui import result_caption, stats_keyboard
from handlers.inline_slideshow import register_slideshow

inline_router = Router(name=__name__)
Expand Down Expand Up @@ -175,12 +175,13 @@ async def _handle_tiktok_inline(
message_id, image_urls, image_data, lang, video_link,
user_id, username, full_name,
client=api, video_info=video_info,
likes=video_info.likes, views=video_info.views,
)
else:
file_id = await upload_photo_to_storage(
image_data, video_link, user_id, username, full_name
)
keyboard = None
keyboard = stats_keyboard(video_info.likes, video_info.views)
if not file_id:
raise ValueError(
"Failed to upload photo to storage. "
Expand Down
6 changes: 6 additions & 0 deletions handlers/get_music.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
from tiktok_api import TikTokClient, TikTokError, ProxyManager
from misc.utils import lang_func, error_catch
from media_types import send_music_result, music_button, get_error_message
from media_types.ui import STATS_CALLBACK_PREFIX

music_router = Router(name=__name__)

# Retry emoji sequence for music download
RETRY_EMOJIS = ["👀", "🤔", "🙏"]


@music_router.callback_query(F.data == STATS_CALLBACK_PREFIX)
async def handle_stats_noop(callback: CallbackQuery):
await callback.answer()


@music_router.callback_query(F.data.startswith("id"))
async def send_tiktok_sound(callback_query: CallbackQuery):
# Vars
Expand Down
80 changes: 57 additions & 23 deletions handlers/inline_slideshow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
_build_storage_caption,
upload_photo_to_storage,
)
from media_types.ui import result_caption
from media_types.ui import result_caption, stats_row
from misc.utils import lang_func
from tiktok_api import TikTokClient, ProxyManager
from tiktok_api.models import VideoInfo
Expand All @@ -44,6 +44,8 @@ class SlideshowSession:
user_id: int
username: str | None
full_name: str | None
likes: int | None = None
views: int | None = None
_cleanup_task: asyncio.Task | None = field(default=None, repr=False)
_loading_indices: set[int] = field(default_factory=set, repr=False)

Expand All @@ -52,18 +54,28 @@ class SlideshowSession:
_refreshing_sessions: set[str] = set() # inline_message_ids currently refreshing


def _build_keyboard(index: int, total: int) -> InlineKeyboardMarkup:
buttons: list[InlineKeyboardButton] = []
def _build_keyboard(
index: int,
total: int,
likes: int | None = None,
views: int | None = None,
) -> InlineKeyboardMarkup:
rows: list[list[InlineKeyboardButton]] = []
sr = stats_row(likes, views)
if sr:
rows.append(sr)
nav: list[InlineKeyboardButton] = []
if index > 0:
buttons.append(InlineKeyboardButton(text="◀️", callback_data="slide:prev"))
buttons.append(
nav.append(InlineKeyboardButton(text="◀️", callback_data="slide:prev"))
nav.append(
InlineKeyboardButton(
text=f"📸 {index + 1}/{total}", callback_data="slide:noop"
)
)
if index < total - 1:
buttons.append(InlineKeyboardButton(text="▶️", callback_data="slide:next"))
return InlineKeyboardMarkup(inline_keyboard=[buttons])
nav.append(InlineKeyboardButton(text="▶️", callback_data="slide:next"))
rows.append(nav)
return InlineKeyboardMarkup(inline_keyboard=rows)


def _compress_url(source_link: str) -> str:
Expand All @@ -88,24 +100,31 @@ def _expand_url(compressed: str) -> str:


def _build_expired_keyboard(
index: int, total: int, source_link: str
index: int,
total: int,
source_link: str,
likes: int | None = None,
views: int | None = None,
) -> InlineKeyboardMarkup:
"""Build a keyboard with counter + refresh button for expired sessions."""
compressed = _compress_url(source_link)
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=f"📸 {index + 1}/{total}",
callback_data="slide:noop",
),
InlineKeyboardButton(
text="🔄",
callback_data=f"sr:{index}:{compressed}",
),
]
rows: list[list[InlineKeyboardButton]] = []
sr = stats_row(likes, views)
if sr:
rows.append(sr)
rows.append(
[
InlineKeyboardButton(
text=f"📸 {index + 1}/{total}",
callback_data="slide:noop",
),
InlineKeyboardButton(
text="🔄",
callback_data=f"sr:{index}:{compressed}",
),
]
)
return InlineKeyboardMarkup(inline_keyboard=rows)


async def _expire_session(inline_message_id: str) -> None:
Expand All @@ -119,6 +138,8 @@ async def _expire_session(inline_message_id: str) -> None:
session.current_index,
len(session.image_urls),
session.source_link,
session.likes,
session.views,
)
await bot.edit_message_reply_markup(
inline_message_id=inline_message_id, reply_markup=keyboard
Expand Down Expand Up @@ -244,6 +265,8 @@ async def register_slideshow(
full_name: str | None,
client: TikTokClient | None = None,
video_info: VideoInfo | None = None,
likes: int | None = None,
views: int | None = None,
) -> tuple[str, InlineKeyboardMarkup]:
"""Download all images, upload as galleries, create session, return (first_file_id, keyboard)."""
file_ids = await _download_and_upload_images(
Expand All @@ -268,10 +291,12 @@ async def register_slideshow(
user_id=user_id,
username=username,
full_name=full_name,
likes=likes,
views=views,
)
_slideshow_sessions[inline_message_id] = session
_reset_ttl(inline_message_id, session)
return first_file_id, _build_keyboard(0, len(image_urls))
return first_file_id, _build_keyboard(0, len(image_urls), likes, views)


def cleanup_all_slideshows() -> None:
Expand Down Expand Up @@ -342,7 +367,7 @@ async def handle_slideshow_callback(callback: CallbackQuery) -> None:
session.current_index = new_index
caption = result_caption(session.lang, session.source_link)
media = InputMediaPhoto(media=file_id, caption=caption)
keyboard = _build_keyboard(new_index, total)
keyboard = _build_keyboard(new_index, total, session.likes, session.views)

await bot.edit_message_media(
inline_message_id=inline_message_id,
Expand Down Expand Up @@ -438,6 +463,13 @@ async def handle_slideshow_refresh(callback: CallbackQuery) -> None:
await callback.answer("Failed to upload images.", show_alert=True)
return

# Extract stats from refreshed TikTok data if available
refresh_likes = None
refresh_views = None
if tiktok_video_info:
refresh_likes = tiktok_video_info.likes
refresh_views = tiktok_video_info.views

# Create new session
session = SlideshowSession(
image_urls=image_urls,
Expand All @@ -448,13 +480,15 @@ async def handle_slideshow_refresh(callback: CallbackQuery) -> None:
user_id=user_id,
username=username,
full_name=full_name,
likes=refresh_likes,
views=refresh_views,
)
_slideshow_sessions[inline_message_id] = session

# Edit message with refreshed image + nav keyboard
caption = result_caption(lang, source_link)
media = InputMediaPhoto(media=file_id, caption=caption)
keyboard = _build_keyboard(index, total)
keyboard = _build_keyboard(index, total, refresh_likes, refresh_views)

await bot.edit_message_media(
inline_message_id=inline_message_id,
Expand Down
2 changes: 1 addition & 1 deletion media_types/send_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ async def process_and_send_images():
if final and len(final) > 0:
await final[0].reply(
result_caption(lang, video_info.link, bool(image_limit)),
reply_markup=music_button(video_id, lang),
reply_markup=music_button(video_id, lang, video_info.likes, video_info.views),
disable_web_page_preview=True,
)

Expand Down
12 changes: 8 additions & 4 deletions media_types/send_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .http_session import download_thumbnail
from .storage import upload_video_to_storage
from .ui import music_button, result_caption
from .ui import music_button, result_caption, stats_keyboard


async def send_video_result(
Expand Down Expand Up @@ -55,7 +55,11 @@ async def send_video_result(
duration=video_duration,
supports_streaming=True,
)
await bot.edit_message_media(inline_message_id=targed_id, media=video_media)
await bot.edit_message_media(
inline_message_id=targed_id,
media=video_media,
reply_markup=stats_keyboard(video_info.likes, video_info.views),
)
return

if isinstance(video_data, bytes):
Expand All @@ -68,7 +72,7 @@ async def send_video_result(
chat_id=targed_id,
document=video_file,
caption=result_caption(lang, video_info.link),
reply_markup=music_button(video_id, lang),
reply_markup=music_button(video_id, lang, video_info.likes, video_info.views),
reply_to_message_id=reply_to_message_id,
disable_content_type_detection=True,
)
Expand All @@ -86,6 +90,6 @@ async def send_video_result(
duration=video_duration,
thumbnail=thumbnail,
supports_streaming=True,
reply_markup=music_button(video_id, lang),
reply_markup=music_button(video_id, lang, video_info.likes, video_info.views),
reply_to_message_id=reply_to_message_id,
)
Loading