├── .gitattributes
├── .gitignore
├── Dockerfile
├── Procfile
├── README.md
├── bot.py
├── cache
└── a.txt
├── config.py
├── html_gen.py
├── requirements.txt
├── static
├── logo.png
├── no-image.png
├── screenshot1.jpg
└── screenshot2.jpg
├── streamer.py
├── templates
├── home.html
└── stream.html
├── utils
├── __init__.py
├── custom_dl.py
├── file_properties.py
└── time_format.py
└── web.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.pyc
3 | *.pyc
4 | *.pyc
5 | minify.py
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10
2 |
3 | WORKDIR /app
4 |
5 | COPY ./requirements.txt /app/requirements.txt
6 |
7 | RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
8 |
9 | COPY . /app
10 |
11 | CMD ["uvicorn", "web:app", "--host", "0.0.0.0", "--port", "80"]
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: uvicorn web:app --host=0.0.0.0 --port=${PORT:-5000}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 | Index Telegram Channels Over Internet
3 |
4 |
5 |
6 | ### Join For Latest Updates
7 |
8 | [](https://telegram.me/TechZBots) [](https://telegram.me/TechZBots_Support)
9 |
10 |
11 |
12 | - Built With Flask In Python
13 | - Fast And Responsive
14 | - Multiple Channel Support
15 | > Index As Many Public Channels As You Want
16 | - Improved Cache System
17 | > Your channel's files on first visit get cached on server, so that userbot doest have to fetch your channel again and again ( to avoid floodwait )
18 |
19 | > You can request the cache to be removed manually by admins of bot
20 |
21 |
22 |
23 | ### ♻️ How To Use ?
24 |
25 | - Make Bot admin in your channel
26 | - Your channel must be public
27 | - Now open this link ```BASE_URL/channel/```
28 |
29 |
30 |
31 | ### Screenshots
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ### Config Vars
40 |
41 | ```
42 | API_ID = ""
43 | API_HASH = ""
44 | BOT_TOKEN = ""
45 | STRING_SESSION = ""
46 | OWNER_ID = ""
47 | ADMINS = [1693701096]
48 | BASE_URL = ""
49 | HOME_PAGE_REDIRECT = "https://techzbots.t.me"
50 | ```
51 |
52 |
53 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | from pyrogram.client import Client
2 | from pyrogram.types import Message
3 | import os
4 | import json
5 |
6 |
7 | def rm_cache(channel=None):
8 | print("Cleaning Cache...")
9 | if not channel:
10 | global image_cache
11 | image_cache = {}
12 | try:
13 | for file in os.listdir("downloads"):
14 | try:
15 | os.remove(f"downloads/{file}")
16 | print(f"Removed {file}")
17 | except Exception as e:
18 | print(e)
19 | except Exception as e:
20 | print(e)
21 | try:
22 | for file in os.listdir("cache"):
23 | try:
24 | if file.endswith(".json"):
25 | if channel:
26 | if file.startswith(channel):
27 | os.remove(f"cache/{file}")
28 | print(f"Removed {file}")
29 | else:
30 | os.remove(f"cache/{file}")
31 | print(f"Removed {file}")
32 | except Exception as e:
33 | print(e)
34 | except Exception as e:
35 | print(e)
36 |
37 |
38 | def get_cache(channel, page):
39 | if os.path.exists(f"cache/{channel}-{page}.json"):
40 | with open(f"cache/{channel}-{page}.json", "r") as f:
41 | return json.load(f)["posts"]
42 | else:
43 | return None
44 |
45 |
46 | def save_cache(channel, cache, page):
47 | with open(f"cache/{channel}-{page}.json", "w") as f:
48 | json.dump(cache, f)
49 |
50 |
51 | async def get_posts(user: Client, channel, page=1):
52 | page = int(page)
53 | cache = get_cache(channel, page)
54 | if cache:
55 | return cache
56 | else:
57 | posts = []
58 | async for post in user.get_chat_history(
59 | chat_id=channel, limit=50, offset=(page - 1) * 50
60 | ):
61 | post: Message
62 | if post.video:
63 | if post.video.file_name:
64 | file_name = post.video.file_name
65 | elif post.caption:
66 | file_name = post.caption
67 | else:
68 | file_name = post.video.file_id
69 |
70 | file_name = file_name[:200]
71 |
72 | title = " ".join(file_name.split(".")[:-1])
73 | posts.append({"msg-id": post.id, "title": title})
74 |
75 | save_cache(channel, {"posts": posts}, page)
76 | return posts
77 |
78 |
79 | image_cache = {}
80 |
81 |
82 | async def get_image(bot: Client, file, channel):
83 | global image_cache
84 |
85 | cache = image_cache.get(f"{channel}-{file}")
86 | if cache:
87 | print(f"Returning img from cache - {channel}-{file}")
88 | return cache
89 |
90 | else:
91 | print(f"Downloading img from Telegram - {channel}-{file}")
92 | msg = await bot.get_messages(channel, int(file))
93 | img = await bot.download_media(str(msg.video.thumbs[0].file_id))
94 | image_cache[f"{channel}-{file}"] = img
95 | return img
96 |
--------------------------------------------------------------------------------
/cache/a.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechShreyash/TechZIndex/39c5cf92678bca46ac923f1ca3f9db445e807358/cache/a.txt
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | API_ID = ""
2 | API_HASH = ""
3 | BOT_TOKEN = ""
4 | STRING_SESSION = ""
5 | OWNER_ID = ""
6 | ADMINS = [1693701096]
7 | BASE_URL = ""
8 | HOME_PAGE_REDIRECT = "https://techzbots.t.me"
--------------------------------------------------------------------------------
/html_gen.py:
--------------------------------------------------------------------------------
1 | def posts_html(posts, channel):
2 | html = ""
3 | phtml = """
4 |
5 |

6 |
7 |
8 |
{title}
9 |
10 |
11 |
"""
12 | for post in posts:
13 | html += phtml.format(
14 | id=post["msg-id"],
15 | img=f"/api/thumb/{channel}/{post['msg-id']}",
16 | title=post["title"],
17 | channel=channel,
18 | )
19 | return html
20 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi
2 | uvicorn
3 | pyrogram
4 | requests
5 | TgCrypto
--------------------------------------------------------------------------------
/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechShreyash/TechZIndex/39c5cf92678bca46ac923f1ca3f9db445e807358/static/logo.png
--------------------------------------------------------------------------------
/static/no-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechShreyash/TechZIndex/39c5cf92678bca46ac923f1ca3f9db445e807358/static/no-image.png
--------------------------------------------------------------------------------
/static/screenshot1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechShreyash/TechZIndex/39c5cf92678bca46ac923f1ca3f9db445e807358/static/screenshot1.jpg
--------------------------------------------------------------------------------
/static/screenshot2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TechShreyash/TechZIndex/39c5cf92678bca46ac923f1ca3f9db445e807358/static/screenshot2.jpg
--------------------------------------------------------------------------------
/streamer.py:
--------------------------------------------------------------------------------
1 | import math
2 | import logging
3 | import mimetypes
4 | import utils
5 | from fastapi.responses import StreamingResponse, Response
6 |
7 | logger = logging.getLogger("streamer")
8 |
9 |
10 | class_cache = {}
11 |
12 |
13 | async def media_streamer(bot, channel, message_id: int, request):
14 | range_header = request.headers.get("Range", 0)
15 |
16 | faster_client = bot
17 |
18 | if faster_client in class_cache:
19 | tg_connect = class_cache[faster_client]
20 | logger.debug(f"Using cached ByteStreamer object for client")
21 | else:
22 | logger.debug(f"Creating new ByteStreamer object for client")
23 | tg_connect = utils.ByteStreamer(faster_client)
24 | class_cache[faster_client] = tg_connect
25 |
26 | logger.debug("before calling get_file_properties")
27 | file_id = await tg_connect.get_file_properties(channel, message_id)
28 | logger.debug("after calling get_file_properties")
29 |
30 | file_size = file_id.file_size
31 |
32 | if range_header:
33 | from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
34 | from_bytes = int(from_bytes)
35 | until_bytes = int(until_bytes) if until_bytes else file_size - 1
36 | else:
37 | from_bytes = 0
38 | until_bytes = file_size - 1
39 |
40 | if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
41 | return Response(
42 | status_code=416,
43 | content="416: Range not satisfiable",
44 | headers={"Content-Range": f"bytes */{file_size}"},
45 | )
46 |
47 | chunk_size = 1024 * 1024
48 | until_bytes = min(until_bytes, file_size - 1)
49 |
50 | offset = from_bytes - (from_bytes % chunk_size)
51 | first_part_cut = from_bytes - offset
52 | last_part_cut = until_bytes % chunk_size + 1
53 |
54 | req_length = until_bytes - from_bytes + 1
55 | part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size)
56 | body = tg_connect.yield_file(
57 | file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size
58 | )
59 | mime_type = file_id.mime_type
60 | file_name = utils.get_name(file_id)
61 | disposition = "attachment"
62 |
63 | if not mime_type:
64 | mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream"
65 |
66 | if "video/" in mime_type or "audio/" in mime_type or "/html" in mime_type:
67 | disposition = "inline"
68 |
69 | return StreamingResponse(
70 | status_code=206 if range_header else 200,
71 | content=body,
72 | headers={
73 | "Content-Type": f"{mime_type}",
74 | "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
75 | "Content-Length": str(req_length),
76 | "Content-Disposition": f'{disposition}; filename="{file_name}"',
77 | "Accept-Ranges": "bytes",
78 | },
79 | media_type=mime_type,
80 | )
81 |
--------------------------------------------------------------------------------
/templates/home.html:
--------------------------------------------------------------------------------
1 | TechZ Index
--------------------------------------------------------------------------------
/templates/stream.html:
--------------------------------------------------------------------------------
1 | TechZ Index
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is a part of TG-FileStreamBot
2 | # Coding : Jyothis Jayanth [@EverythingSuckz]
3 |
4 | from .time_format import get_readable_time
5 | from .file_properties import get_hash, get_name
6 | from .custom_dl import ByteStreamer
--------------------------------------------------------------------------------
/utils/custom_dl.py:
--------------------------------------------------------------------------------
1 | import math
2 | import asyncio
3 | import logging
4 | from typing import Dict, Union
5 | from pyrogram import Client, utils, raw
6 | from .file_properties import get_file_ids
7 | from pyrogram.session import Session, Auth
8 | from pyrogram.errors import AuthBytesInvalid
9 | from pyrogram.file_id import FileId, FileType, ThumbnailSource
10 |
11 | logger = logging.getLogger("streamer")
12 |
13 |
14 | class ByteStreamer:
15 | def __init__(self, client: Client):
16 | """A custom class that holds the cache of a specific client and class functions.
17 | attributes:
18 | client: the client that the cache is for.
19 | cached_file_ids: a dict of cached file IDs.
20 | cached_file_properties: a dict of cached file properties.
21 |
22 | functions:
23 | generate_file_properties: returns the properties for a media of a specific message contained in Tuple.
24 | generate_media_session: returns the media session for the DC that contains the media file.
25 | yield_file: yield a file from telegram servers for streaming.
26 |
27 | This is a modified version of the
28 | Thanks to Eyaadh
29 | """
30 | self.clean_timer = 30 * 60
31 | self.client: Client = client
32 | self.cached_file_ids: Dict[int, FileId] = {}
33 | asyncio.create_task(self.clean_cache())
34 |
35 | async def get_file_properties(self, channel, message_id: int) -> FileId:
36 | """
37 | Returns the properties of a media of a specific message in a FIleId class.
38 | if the properties are cached, then it'll return the cached results.
39 | or it'll generate the properties from the Message ID and cache them.
40 | """
41 | if message_id not in self.cached_file_ids:
42 | await self.generate_file_properties(channel, message_id)
43 | logger.debug(f"Cached file properties for message with ID {message_id}")
44 | return self.cached_file_ids[message_id]
45 |
46 | async def generate_file_properties(self, channel, message_id: int) -> FileId:
47 | """
48 | Generates the properties of a media file on a specific message.
49 | returns ths properties in a FIleId class.
50 | """
51 | file_id = await get_file_ids(self.client, channel, message_id)
52 | logger.debug(
53 | f"Generated file ID and Unique ID for message with ID {message_id}"
54 | )
55 | if not file_id:
56 | logger.debug(f"Message with ID {message_id} not found")
57 | raise Exception("FileNotFound")
58 | self.cached_file_ids[message_id] = file_id
59 | logger.debug(f"Cached media message with ID {message_id}")
60 | return self.cached_file_ids[message_id]
61 |
62 | async def generate_media_session(self, client: Client, file_id: FileId) -> Session:
63 | """
64 | Generates the media session for the DC that contains the media file.
65 | This is required for getting the bytes from Telegram servers.
66 | """
67 |
68 | media_session = client.media_sessions.get(file_id.dc_id, None)
69 |
70 | if media_session is None:
71 | if file_id.dc_id != await client.storage.dc_id():
72 | media_session = Session(
73 | client,
74 | file_id.dc_id,
75 | await Auth(
76 | client, file_id.dc_id, await client.storage.test_mode()
77 | ).create(),
78 | await client.storage.test_mode(),
79 | is_media=True,
80 | )
81 | await media_session.start()
82 |
83 | for _ in range(6):
84 | exported_auth = await client.invoke(
85 | raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id)
86 | )
87 |
88 | try:
89 | await media_session.invoke(
90 | raw.functions.auth.ImportAuthorization(
91 | id=exported_auth.id, bytes=exported_auth.bytes
92 | )
93 | )
94 | break
95 | except AuthBytesInvalid:
96 | logger.debug(
97 | f"Invalid authorization bytes for DC {file_id.dc_id}"
98 | )
99 | continue
100 | else:
101 | await media_session.stop()
102 | raise AuthBytesInvalid
103 | else:
104 | media_session = Session(
105 | client,
106 | file_id.dc_id,
107 | await client.storage.auth_key(),
108 | await client.storage.test_mode(),
109 | is_media=True,
110 | )
111 | await media_session.start()
112 | logger.debug(f"Created media session for DC {file_id.dc_id}")
113 | client.media_sessions[file_id.dc_id] = media_session
114 | else:
115 | logger.debug(f"Using cached media session for DC {file_id.dc_id}")
116 | return media_session
117 |
118 | @staticmethod
119 | async def get_location(
120 | file_id: FileId,
121 | ) -> Union[
122 | raw.types.InputPhotoFileLocation,
123 | raw.types.InputDocumentFileLocation,
124 | raw.types.InputPeerPhotoFileLocation,
125 | ]:
126 | """
127 | Returns the file location for the media file.
128 | """
129 | file_type = file_id.file_type
130 |
131 | if file_type == FileType.CHAT_PHOTO:
132 | if file_id.chat_id > 0:
133 | peer = raw.types.InputPeerUser(
134 | user_id=file_id.chat_id, access_hash=file_id.chat_access_hash
135 | )
136 | else:
137 | if file_id.chat_access_hash == 0:
138 | peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id)
139 | else:
140 | peer = raw.types.InputPeerChannel(
141 | channel_id=utils.get_channel_id(file_id.chat_id),
142 | access_hash=file_id.chat_access_hash,
143 | )
144 |
145 | location = raw.types.InputPeerPhotoFileLocation(
146 | peer=peer,
147 | volume_id=file_id.volume_id,
148 | local_id=file_id.local_id,
149 | big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG,
150 | )
151 | elif file_type == FileType.PHOTO:
152 | location = raw.types.InputPhotoFileLocation(
153 | id=file_id.media_id,
154 | access_hash=file_id.access_hash,
155 | file_reference=file_id.file_reference,
156 | thumb_size=file_id.thumbnail_size,
157 | )
158 | else:
159 | location = raw.types.InputDocumentFileLocation(
160 | id=file_id.media_id,
161 | access_hash=file_id.access_hash,
162 | file_reference=file_id.file_reference,
163 | thumb_size=file_id.thumbnail_size,
164 | )
165 | return location
166 |
167 | async def yield_file(
168 | self,
169 | file_id: FileId,
170 | offset: int,
171 | first_part_cut: int,
172 | last_part_cut: int,
173 | part_count: int,
174 | chunk_size: int,
175 | ) -> Union[str, None]:
176 | """
177 | Custom generator that yields the bytes of the media file.
178 | Modded from
179 | Thanks to Eyaadh
180 | """
181 | client = self.client
182 | logger.debug(f"Starting to yielding file with client.")
183 | media_session = await self.generate_media_session(client, file_id)
184 |
185 | current_part = 1
186 | location = await self.get_location(file_id)
187 |
188 | try:
189 | r = await media_session.invoke(
190 | raw.functions.upload.GetFile(
191 | location=location, offset=offset, limit=chunk_size
192 | ),
193 | )
194 | if isinstance(r, raw.types.upload.File):
195 | while True:
196 | chunk = r.bytes
197 | if not chunk:
198 | break
199 | elif part_count == 1:
200 | yield chunk[first_part_cut:last_part_cut]
201 | elif current_part == 1:
202 | yield chunk[first_part_cut:]
203 | elif current_part == part_count:
204 | yield chunk[:last_part_cut]
205 | else:
206 | yield chunk
207 |
208 | current_part += 1
209 | offset += chunk_size
210 |
211 | if current_part > part_count:
212 | break
213 |
214 | r = await media_session.invoke(
215 | raw.functions.upload.GetFile(
216 | location=location, offset=offset, limit=chunk_size
217 | ),
218 | )
219 | except (TimeoutError, AttributeError):
220 | pass
221 | finally:
222 | logger.debug(f"Finished yielding file with {current_part} parts.")
223 |
224 | async def clean_cache(self) -> None:
225 | """
226 | function to clean the cache to reduce memory usage
227 | """
228 | while True:
229 | await asyncio.sleep(self.clean_timer)
230 | self.cached_file_ids.clear()
231 | logger.debug("Cleaned the cache")
232 |
--------------------------------------------------------------------------------
/utils/file_properties.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | from pyrogram import Client
3 | from pyrogram.types import Message
4 | from pyrogram.file_id import FileId
5 | from typing import Any, Optional, Union
6 | from pyrogram.raw.types.messages import Messages
7 | from datetime import datetime
8 |
9 |
10 | async def parse_file_id(message: "Message") -> Optional[FileId]:
11 | media = get_media_from_message(message)
12 | if media:
13 | return FileId.decode(media.file_id)
14 |
15 |
16 | async def parse_file_unique_id(message: "Messages") -> Optional[str]:
17 | media = get_media_from_message(message)
18 | if media:
19 | return media.file_unique_id
20 |
21 |
22 | async def get_file_ids(client: Client, chat_id, message_id) -> Optional[FileId]:
23 | message = await client.get_messages(chat_id, int(message_id))
24 | if message.empty:
25 | raise Exception("FileNotFound")
26 | media = get_media_from_message(message)
27 | file_unique_id = await parse_file_unique_id(message)
28 | file_id = await parse_file_id(message)
29 | setattr(file_id, "file_size", getattr(media, "file_size", 0))
30 | setattr(file_id, "mime_type", getattr(media, "mime_type", ""))
31 | setattr(file_id, "file_name", getattr(media, "file_name", ""))
32 | setattr(file_id, "unique_id", file_unique_id)
33 | return file_id
34 |
35 |
36 | def get_media_from_message(message: "Message") -> Any:
37 | media_types = (
38 | "audio",
39 | "document",
40 | "photo",
41 | "sticker",
42 | "animation",
43 | "video",
44 | "voice",
45 | "video_note",
46 | )
47 | for attr in media_types:
48 | media = getattr(message, attr, None)
49 | if media:
50 | return media
51 |
52 |
53 | def get_hash(media_msg: Union[str, Message], length: int) -> str:
54 | if isinstance(media_msg, Message):
55 | media = get_media_from_message(media_msg)
56 | unique_id = getattr(media, "file_unique_id", "")
57 | else:
58 | unique_id = media_msg
59 | long_hash = hashlib.sha256(unique_id.encode("UTF-8")).hexdigest()
60 | return long_hash[:length]
61 |
62 |
63 | def get_name(media_msg: Union[Message, FileId]) -> str:
64 | if isinstance(media_msg, Message):
65 | media = get_media_from_message(media_msg)
66 | file_name = getattr(media, "file_name", "")
67 |
68 | elif isinstance(media_msg, FileId):
69 | file_name = getattr(media_msg, "file_name", "")
70 |
71 | if not file_name:
72 | if isinstance(media_msg, Message) and media_msg.media:
73 | media_type = media_msg.media.value
74 | elif media_msg.file_type:
75 | media_type = media_msg.file_type.name.lower()
76 | else:
77 | media_type = "file"
78 |
79 | formats = {
80 | "photo": "jpg",
81 | "audio": "mp3",
82 | "voice": "ogg",
83 | "video": "mp4",
84 | "animation": "mp4",
85 | "video_note": "mp4",
86 | "sticker": "webp",
87 | }
88 |
89 | ext = formats.get(media_type)
90 | ext = "." + ext if ext else ""
91 |
92 | date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
93 | file_name = f"{media_type}-{date}{ext}"
94 |
95 | return file_name
96 |
--------------------------------------------------------------------------------
/utils/time_format.py:
--------------------------------------------------------------------------------
1 | def get_readable_time(seconds: int) -> str:
2 | count = 0
3 | readable_time = ""
4 | time_list = []
5 | time_suffix_list = ["s", "m", "h", " days"]
6 | while count < 4:
7 | count += 1
8 | if count < 3:
9 | remainder, result = divmod(seconds, 60)
10 | else:
11 | remainder, result = divmod(seconds, 24)
12 | if seconds == 0 and remainder == 0:
13 | break
14 | time_list.append(int(result))
15 | seconds = int(remainder)
16 | for x in range(len(time_list)):
17 | time_list[x] = str(time_list[x]) + time_suffix_list[x]
18 | if len(time_list) == 4:
19 | readable_time += time_list.pop() + ", "
20 | time_list.reverse()
21 | readable_time += ": ".join(time_list)
22 | return readable_time
23 |
--------------------------------------------------------------------------------
/web.py:
--------------------------------------------------------------------------------
1 | from streamer import media_streamer
2 | from fastapi import FastAPI, Request
3 | from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse, Response
4 | from bot import get_image, get_posts, rm_cache
5 | from html_gen import posts_html
6 | from pyrogram.client import Client
7 | from config import *
8 | from pyrogram import filters
9 | from pyrogram.types import Message
10 |
11 | user = Client(
12 | "userbot",
13 | api_id=int(API_ID),
14 | api_hash=API_HASH,
15 | session_string=STRING_SESSION,
16 | )
17 | bot = Client(
18 | "techzindexbot",
19 | api_id=int(API_ID),
20 | api_hash=API_HASH,
21 | bot_token=BOT_TOKEN,
22 | )
23 |
24 | app = FastAPI(docs_url=None, redoc_url=None)
25 |
26 | with open("templates/home.html") as f:
27 | HOME_HTML = f.read()
28 | with open("templates/stream.html") as f:
29 | STREAM_HTML = f.read()
30 |
31 |
32 | @app.get("/")
33 | async def home():
34 | return RedirectResponse(HOME_PAGE_REDIRECT)
35 |
36 |
37 | @app.get("/channel/{channel}")
38 | async def channel(channel):
39 | posts = await get_posts(user, str(channel).lower())
40 | phtml = posts_html(posts, channel)
41 | return HTMLResponse(
42 | HOME_HTML.replace("POSTS", phtml).replace("CHANNEL_ID", channel)
43 | )
44 |
45 |
46 | @app.get("/api/posts/{channel}/{page}")
47 | async def get_posts_api(channel, page: str):
48 | posts = await get_posts(user, str(channel).lower(), page)
49 | phtml = posts_html(posts, channel)
50 | return {"html": phtml}
51 |
52 |
53 | @app.get("/static/{file}")
54 | async def static_files(file):
55 | return FileResponse(f"static/{file}")
56 |
57 |
58 | @app.get("/api/thumb/{channel}/{id}")
59 | async def get_thumb(channel, id):
60 | img = await get_image(bot, id, channel)
61 | return FileResponse(img, media_type="image/jpeg")
62 |
63 |
64 | @app.on_event("startup")
65 | async def startup():
66 | print("Starting TG Clients...")
67 | await bot.start()
68 | await user.start()
69 | print("TG Clients Started")
70 | print("========================================")
71 | print("TechZIndex Started Successfully")
72 | print("Made By TechZBots | TechShreyash")
73 | print("========================================")
74 |
75 |
76 | # Streamer
77 |
78 |
79 | @app.get("/stream/{channel}/{id}")
80 | async def stream(channel, id):
81 | return HTMLResponse(
82 | STREAM_HTML.replace("URL", f"{BASE_URL}/api/stream/{channel}/{id}")
83 | )
84 |
85 |
86 | @app.get("/api/stream/{channel}/{id}")
87 | async def stream_api(channel, id, request: Request):
88 | return await media_streamer(bot, channel, id, request)
89 |
90 |
91 | # bot commands
92 |
93 |
94 | @bot.on_message(filters.command("start"))
95 | async def start_cmd(_, msg: Message):
96 | await msg.reply_text(
97 | "TechZIndex Up and Running\n\n/clean_cache to clean website cache\n/help to know how to use this bot\n\nMade By @TechZBots | @TechZBots_Support"
98 | )
99 |
100 |
101 | @bot.on_message(filters.command("help"))
102 | async def help_cmd(_, msg: Message):
103 | await msg.reply_text(
104 | f"""
105 | **How to use this bot?**
106 |
107 | 1. Add me to your channel as admin
108 | 2. Your channel must be public
109 | 3. Now open this link domain/channel/
110 |
111 | Ex : http://index.techzbots.live/channel/autoairinganimes
112 |
113 | Contact [Owner](tg://user?id={OWNER_ID}) To Get domain of website
114 | Owner Id : {OWNER_ID}"""
115 | )
116 |
117 |
118 | @bot.on_message(filters.command("clean_cache"))
119 | async def clean_cache(_, msg: Message):
120 | if msg.from_user.id in ADMINS:
121 | x = msg.text.split(" ")
122 | if len(x) == 2:
123 | rm_cache(x[1])
124 | else:
125 | rm_cache()
126 | await msg.reply_text("Cache cleaned")
127 | else:
128 | await msg.reply_text(
129 | "You are not my owner\n\nContact [Owner](tg://user?id={OWNER_ID}) If You Want To Update Your Site\n\nRead : https://t.me/TechZBots/524"
130 | )
131 |
--------------------------------------------------------------------------------