Skip to content
Open
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
18 changes: 9 additions & 9 deletions bot/helper/chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ async def get_chats():
async def posts_chat(channels):
phtml = """
<div class="col nm-platform-col">
<a href="/channel/{cid}" class="nm-platform-link" aria-label="Open {title}">
<article class="nm-platform-card">
<a href="/channel/{cid}" class="nm-platform-link" aria-label="Open {title}" data-channel-id="{cid}" data-channel-title="{title}" onclick="storeSelectedPlatform(event, this)">
<article class="nm-platform-card nm-platform-card--featured">
<div class="nm-platform-logo-wrap">
<img src="https://cdn.jsdelivr.net/gh/weebzone/weebzone/data/Surf-TG/src/loading.gif"
class="lzy_img nm-platform-logo" data-src="{img}" loading="lazy" alt="{title}">
<img src="{img}"
class="nm-platform-logo" loading="lazy" alt="{title}" onerror="this.onerror=null;this.src='/static/logo.png';">
</div>
<div class="nm-platform-copy">
<span class="nm-platform-kicker">Telegram OTT</span>
<span class="nm-platform-kicker">360Hub OTT</span>
<h6 class="nm-platform-title">{title}</h6>
<span class="nm-platform-pill">{ctype}</span>
</div>
Expand All @@ -45,8 +45,8 @@ async def post_playlist(playlists):
</a>
<a href="/playlist?db={cid}" class="nm-media-link">
<div class="nm-media-poster-wrap">
<img src="https://cdn.jsdelivr.net/gh/weebzone/weebzone/data/Surf-TG/src/loading.gif"
class="lzy_img nm-media-poster" data-src="{img}" loading="lazy" alt="{title}">
<img src="{img}"
class="nm-media-poster" loading="lazy" alt="{title}" onerror="this.onerror=null;this.src='/static/thumbnail.jpg';">
<div class="nm-media-overlay"></div>
<div class="nm-play-chip"><i class="bi bi-play-fill"></i></div>
</div>
Expand All @@ -73,8 +73,8 @@ async def posts_db_file(posts):
class="bi bi-pencil-square"></i></a>
<a href="/watch/{chat_id}?id={id}&hash={hash}" class="nm-media-link">
<div class="nm-media-poster-wrap">
<img src="https://cdn.jsdelivr.net/gh/weebzone/weebzone/data/Surf-TG/src/loading.gif" data-src="{img}"
class="lzy_img nm-media-poster" loading="lazy" alt="{title}">
<img src="{img}"
class="nm-media-poster" loading="lazy" alt="{title}" onerror="this.onerror=null;this.src='/static/thumbnail.jpg';">
<div class="nm-media-overlay"></div>
<div class="nm-play-chip"><i class="bi bi-play-fill"></i></div>
</div>
Expand Down
56 changes: 38 additions & 18 deletions bot/helper/index.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
from os.path import splitext
import re
from asyncio import gather, to_thread
from bot.config import Telegram
from bot.helper.database import Database
from bot.telegram import StreamBot, UserBot
from bot.helper.file_size import get_readable_file_size
from bot.helper.cache import get_cache, save_cache
from bot.helper.tmdb import fetch_poster
from asyncio import gather
from bot.helper.tmdb import fetch_poster, FALLBACK_POSTER


db = Database()


def normalize_post_title(caption, fallback_name=None):
raw_title = caption or fallback_name or "Untitled"
title, _ = splitext(raw_title)
title = re.sub(r"[.,|_\',]", " ", title)
title = re.sub(r"\s+", " ", title).strip()
return title or "Untitled"


async def fetch_message(chat_id, message_id):
try:
message = await StreamBot.get_messages(chat_id, message_id)
return message
except Exception as e:
except Exception:
return None


Expand All @@ -29,35 +38,46 @@ async def get_messages(chat_id, first_message_id, last_message_id, batch_size=50
for message in batch_messages:
if message:
if file := message.video or message.document:
title = message.caption #file.file_name or message.caption or file.file_id
title, _ = splitext(title)
title = re.sub(r'[.,|_\',]', ' ', title)
title = normalize_post_title(message.caption, file.file_name)
messages.append({"msg_id": message.id, "title": title,
"hash": file.file_unique_id[:6], "size": get_readable_file_size(file.file_size),
"type": file.mime_type, "chat_id": str(chat_id)})
current_message_id += batch_size
return messages


async def _build_post_entry(post):
file = post.video or post.document
if not file:
return None

title = normalize_post_title(post.caption, file.file_name)
poster = await to_thread(fetch_poster, title)
return {
"msg_id": post.id,
"title": title,
"poster_url": poster or FALLBACK_POSTER,
"hash": file.file_unique_id[:6],
"size": get_readable_file_size(file.file_size),
"type": file.mime_type,
}


async def get_files(chat_id, page=1):
if Telegram.SESSION_STRING == '':
return await db.list_tgfiles(id=chat_id, page=page)
if cache := get_cache(chat_id, int(page)):
return cache
posts = []
history = []
async for post in UserBot.get_chat_history(chat_id=int(chat_id), limit=50, offset=(int(page) - 1) * 50):
file = post.video or post.document
if not file:
continue
title = post.caption
title, _ = splitext(title)
title = re.sub(r'[.,|_\',]', ' ', title)
poster = fetch_poster(title)
posts.append({"msg_id": post.id, "title": title, "poster_url": poster,
"hash": file.file_unique_id[:6], "size": get_readable_file_size(file.file_size), "type": file.mime_type})
history.append(post)

built_posts = await gather(*[_build_post_entry(post) for post in history])
posts = [post for post in built_posts if post]
save_cache(chat_id, {"posts": posts}, page)
return posts


async def posts_file(posts, chat_id):
phtml = """
<div class="col nm-rail-col">
Expand All @@ -67,8 +87,8 @@ async def posts_file(posts, chat_id):
data-id="{id}|{hash}|{title}|{size}|{type}|{img}">
<a href="/watch/{chat_id}?id={id}&hash={hash}" class="nm-media-link">
<div class="nm-media-poster-wrap">
<img src="https://cdn.jsdelivr.net/gh/weebzone/weebzone/data/Surf-TG/src/loading.gif" class="lzy_img nm-media-poster"
data-src="{img}" loading="lazy" alt="{title}"
<img src="{img}" class="nm-media-poster"
loading="lazy" alt="{title}"
onerror="this.onerror=null;this.src='https://cdn-icons-png.flaticon.com/512/565/565547.png';">
<div class="nm-media-overlay"></div>
<div class="nm-play-chip"><i class="bi bi-play-fill"></i></div>
Expand Down
44 changes: 30 additions & 14 deletions bot/helper/search.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import re
from asyncio import gather, to_thread

from bot.config import Telegram
from bot.helper.database import Database
from bot.telegram import UserBot
from os.path import splitext
from bot.helper.tmdb import fetch_poster
from bot.helper.file_size import get_readable_file_size
from bot.helper.index import normalize_post_title
from bot.helper.tmdb import fetch_poster, FALLBACK_POSTER


db = Database()


async def _build_search_entry(post):
file = post.video or post.document
if not file:
return None

title = normalize_post_title(post.caption, file.file_name)
poster = await to_thread(fetch_poster, title)
return {
"msg_id": post.id,
"title": title,
"poster_url": poster or FALLBACK_POSTER,
"hash": file.file_unique_id[:6],
"size": get_readable_file_size(file.file_size),
"type": file.mime_type,
}


async def search(chat_id, query, page):
if Telegram.SESSION_STRING == '':
return await db.search_tgfiles(id=chat_id, query=query, page=page)
posts = []

matches = []
async for post in UserBot.search_messages(chat_id=int(chat_id), limit=50, query=str(query), offset=(int(page) - 1) * 50):
file = post.video or post.document
if not file:
continue
title = post.caption
title, _ = splitext(title)
title = re.sub(r'[.,|_\',]', ' ', title)
poster = fetch_poster(title)
posts.append({"msg_id": post.id, "title": title, "poster_url": poster,
"hash": file.file_unique_id[:6], "size": get_readable_file_size(file.file_size), "type": file.mime_type})
return posts
matches.append(post)

built_posts = await gather(*[_build_search_entry(post) for post in matches])
return [post for post in built_posts if post]
4 changes: 2 additions & 2 deletions bot/server/template/dl.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" href="https://homies-theta.vercel.app/logo.png" type="image/x-icon"><link rel="shortcut icon" href="https://homies-theta.vercel.app/logo.png" type="image/x-icon"><title>AniHix: <!-- Filename --> </title><link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://bootswatch.com/5/<!-- Theme -->/bootstrap.min.css"><style>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="icon" href="https://homies-theta.vercel.app/logo.png" type="image/x-icon"><link rel="shortcut icon" href="https://homies-theta.vercel.app/logo.png" type="image/x-icon"><title>360Hub: <!-- Filename --> </title><meta name="description" content="Download files from 360Hub."><link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://bootswatch.com/5/<!-- Theme -->/bootstrap.min.css"><style>
:root {
--bg: #07080d;
--bg-soft: #11141d;
Expand Down Expand Up @@ -225,4 +225,4 @@
.ott-app-new .nm-platform-logo-wrap { width: 60px; height: 60px; flex-basis: 60px; }
}

</style></head><body class="ott-app-new"><nav class="navbar navbar-expand-lg ott-navbar" data-bs-theme="dark"><div class="ott-shell container-fluid py-2"><a class="navbar-brand brand-wrap" href="/"><span class="brand-mark"><img src="https://homies-theta.vercel.app/logo.png" alt="Logo"></span><span class="brand-copy"><small>Direct access</small><strong>AniHix</strong></span></a><div><form id="signoutForm" action="/logout" method="post"><button type="submit" class="btn btn-primary"><i class="bi bi-box-arrow-left"></i> Logout</button></form></div></div></nav><main class="ott-shell py-5 fade-in"><section class="glass-card p-4 mx-auto" style="max-width:760px;"><div class="eyebrow mb-2">Download center</div><h1 class="h3 fw-bold mb-3"><!-- Filename --></h1><div class="meta-row mb-4"><span class="tile-badge"><i class="bi bi-hdd"></i> <!-- Size --></span><span class="tile-badge"><i class="bi bi-telegram"></i> Telegram linked</span></div><p class="section-copy">Download links and Telegram deep links use the same existing URL generation and backend behavior—this page only modernizes the presentation.</p><div class="action-cluster download-actions mt-4 justify-content-center"><div onclick="download()" class="btn btn-primary">Download</div><div onclick="get_file()" class="btn btn-secondary">Get File</div></div></section></main><footer class="footer-ott"><div class="ott-shell">Made with ❤️ by <a href="https://t.me/Uchiha_Developer" target="_blank">Sasuke Uchiha</a><br>&copy; 2025 <a href="https://t.me/Uchiha_Developer" target="_blank">AniHix</a>. All Rights Reserved.</div></footer><script>const videolink = window.location.href; const url = new URL(videolink); const domainUrl = url.origin; const videoIdWithParams = videolink.split('/').pop(); const videoId = videoIdWithParams.split('?')[0]; const idParam = new URLSearchParams(window.location.search).get('id'); const hashParam = new URLSearchParams(window.location.search).get('hash'); const encodedName = encodeURIComponent('<!-- Filename -->'); const downloadlink = `${domainUrl}/${videoId}/${encodedName}?id=${idParam}&hash=${hashParam}`; function download() { window.location.href = downloadlink; } function get_file() { const regex = /(\d+)\?id=(\d+)&/; const match = videolink.match(regex); if (match) { const chatId = "-100" + match[1]; const messageId = match[2]; const openTG = `https://t.me/<!-- Username -->?start=file_${messageId}${chatId}`; window.location.href = openTG; } } function copyUrl() { const url = downloadlink; navigator.clipboard.writeText(url).then(() => { alert('URL copied to clipboard!'); }).catch((error) => { console.error('Failed to copy URL: ', error); }); }</script><script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool'></script></body></html>
</style></head><body class="ott-app-new"><nav class="navbar navbar-expand-lg ott-navbar" data-bs-theme="dark"><div class="ott-shell container-fluid py-2"><a class="navbar-brand brand-wrap" href="/"><span class="brand-mark"><img src="https://homies-theta.vercel.app/logo.png" alt="Logo"></span><span class="brand-copy"><small>Direct access</small><strong>360Hub</strong></span></a><div><form id="signoutForm" action="/logout" method="post"><button type="submit" class="btn btn-primary"><i class="bi bi-box-arrow-left"></i> Logout</button></form></div></div></nav><main class="ott-shell py-5 fade-in"><section class="glass-card p-4 mx-auto" style="max-width:760px;"><div class="eyebrow mb-2">Download center</div><h1 class="h3 fw-bold mb-3"><!-- Filename --></h1><div class="meta-row mb-4"><span class="tile-badge"><i class="bi bi-hdd"></i> <!-- Size --></span><span class="tile-badge"><i class="bi bi-telegram"></i> Telegram linked</span></div><p class="section-copy">Download links and Telegram deep links use the same existing URL generation and backend behavior—this page only modernizes the presentation.</p><div class="action-cluster download-actions mt-4 justify-content-center"><div onclick="download()" class="btn btn-primary">Download</div><div onclick="get_file()" class="btn btn-secondary">Get File</div></div></section></main><footer class="footer-ott"><div class="ott-shell">Made with ❤️ by <a href="https://t.me/Uchiha_Developer" target="_blank">Sasuke Uchiha</a><br>&copy; 2025 <a href="https://t.me/Uchiha_Developer" target="_blank">360Hub</a>. All Rights Reserved.</div></footer><script>const videolink = window.location.href; const url = new URL(videolink); const domainUrl = url.origin; const videoIdWithParams = videolink.split('/').pop(); const videoId = videoIdWithParams.split('?')[0]; const idParam = new URLSearchParams(window.location.search).get('id'); const hashParam = new URLSearchParams(window.location.search).get('hash'); const encodedName = encodeURIComponent('<!-- Filename -->'); const downloadlink = `${domainUrl}/${videoId}/${encodedName}?id=${idParam}&hash=${hashParam}`; function download() { window.location.href = downloadlink; } function get_file() { const regex = /(\d+)\?id=(\d+)&/; const match = videolink.match(regex); if (match) { const chatId = "-100" + match[1]; const messageId = match[2]; const openTG = `https://t.me/<!-- Username -->?start=file_${messageId}${chatId}`; window.location.href = openTG; } } function copyUrl() { const url = downloadlink; navigator.clipboard.writeText(url).then(() => { alert('URL copied to clipboard!'); }).catch((error) => { console.error('Failed to copy URL: ', error); }); }</script><script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool'></script></body></html>
Loading