From 2c0160b21ed78ca75b0b0e5ac34c4bfb12df1c59 Mon Sep 17 00:00:00 2001 From: odysseusmax Date: Sat, 12 Dec 2020 19:46:44 +0530 Subject: [PATCH] new changes --- .gitignore | 1 + app/config.py | 9 +- app/generate_session_string.py | 6 +- app/routes.py | 83 +++--- app/telegram.py | 4 +- app/templates/info.html | 41 +-- app/views.py | 483 --------------------------------- app/views/__init__.py | 39 +++ app/views/download.py | 78 ++++++ app/views/home_view.py | 19 ++ app/views/index_view.py | 99 +++++++ app/views/info_view.py | 90 ++++++ app/views/logo_view.py | 63 +++++ app/views/thumbnail_view.py | 71 +++++ app/views/wildcard_view.py | 7 + 15 files changed, 539 insertions(+), 554 deletions(-) delete mode 100644 app/views.py create mode 100644 app/views/__init__.py create mode 100644 app/views/download.py create mode 100644 app/views/home_view.py create mode 100644 app/views/index_view.py create mode 100644 app/views/info_view.py create mode 100644 app/views/logo_view.py create mode 100644 app/views/thumbnail_view.py create mode 100644 app/views/wildcard_view.py diff --git a/.gitignore b/.gitignore index 1efcff8..3a36d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ venv/ *.sh +logo/ diff --git a/app/config.py b/app/config.py index 2e10253..f72e972 100644 --- a/app/config.py +++ b/app/config.py @@ -1,3 +1,4 @@ +from pathlib import Path import traceback import json import sys @@ -24,8 +25,6 @@ except (KeyError, ValueError): try: index_settings_str = os.environ["INDEX_SETTINGS"].strip() index_settings = json.loads(index_settings_str) - otg_settings = index_settings['otg'] - enable_otg = otg_settings['enable'] except: traceback.print_exc() print("\n\nPlease set the INDEX_SETTINGS environment variable correctly") @@ -40,5 +39,7 @@ except (KeyError, ValueError): host = os.environ.get("HOST", "0.0.0.0") debug = bool(os.environ.get("DEBUG")) -chat_ids = [] -alias_ids = [] +block_downloads = bool(os.environ.get("BLOCK_DOWNLOADS")) +results_per_page = int(os.environ.get("RESULTS_PER_PAGE", "20")) +logo_folder = Path('logo/') +logo_folder.mkdir(exist_ok=True) diff --git a/app/generate_session_string.py b/app/generate_session_string.py index 9d1f258..124922b 100644 --- a/app/generate_session_string.py +++ b/app/generate_session_string.py @@ -1,8 +1,10 @@ +import os + from telethon.sync import TelegramClient from telethon.sessions import StringSession -api_id = int(input("Enter your API_ID: ")) -api_hash = input("Enter your API_HASH: ") +api_id = int(os.environ.get('API_ID') or input("Enter your API_ID: ")) +api_hash = os.environ.get('API_HASH') or input("Enter your API_HASH: ") with TelegramClient(StringSession(), api_id, api_hash) as client: print(client.session.save()) diff --git a/app/routes.py b/app/routes.py index 855f495..5973b5b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,75 +3,70 @@ import string import logging from aiohttp import web +from telethon.tl.types import Channel, Chat, User -from .config import index_settings, alias_ids, chat_ids +from .config import index_settings log = logging.getLogger(__name__) -def generate_alias_id(chat): - chat_id = chat.id - title = chat.title - while True: - alias_id = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(len(str(chat_id)))]) - if alias_id in alias_ids: - continue - alias_ids.append(alias_id) - chat_ids.append({ - 'chat_id': chat_id, - 'alias_id': alias_id, - 'title': title - }) - return alias_id - - async def setup_routes(app, handler): h = handler client = h.client - p = r"/{chat:[^/]+}" - routes = [ - web.get('/', h.home), - web.post('/otg', h.dynamic_view), - web.get('/otg', h.otg_view), - web.get(p, h.index), - web.get(p + r"/logo", h.logo), - web.get(p + r"/{id:\d+}/view", h.info), - web.get(p + r"/{id:\d+}/download", h.download_get), - web.head(p + r"/{id:\d+}/download", h.download_head), - web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get), - web.view(r'/{wildcard:.*}', h.wildcard) - ] index_all = index_settings['index_all'] index_private = index_settings['index_private'] index_group = index_settings['index_group'] index_channel = index_settings['index_channel'] exclude_chats = index_settings['exclude_chats'] include_chats = index_settings['include_chats'] + routes = [ + web.get('/', h.home) + ] if index_all: + #print(await client.get_dialogs()) async for chat in client.iter_dialogs(): alias_id = None if chat.id in exclude_chats: continue - if chat.is_user: - if index_private: - alias_id = generate_alias_id(chat) - elif chat.is_channel: - if index_channel: - alias_id = generate_alias_id(chat) - else: - if index_group: - alias_id = generate_alias_id(chat) + entity = chat.entity - if not alias_id: + if isinstance(entity, User) and not index_private: + print(f'{chat.title}, private: {index_private}') continue - log.debug(f"Index added for {chat.id} :: {chat.title} at /{alias_id}") + elif isinstance(entity, Channel) and not index_channel: + print(f'{chat.title}, channel: {index_channel}') + continue + elif isinstance(entity, Chat) and not index_group: + print(f'{chat.title}, group: {index_group}') + continue + + alias_id = h.generate_alias_id(chat) + p = "/{chat:" + alias_id + "}" + routes.extend([ + web.get(p, h.index), + web.get(p + r"/logo", h.logo), + web.get(p + r"/{id:\d+}/view", h.info), + web.get(p + r"/{id:\d+}/download", h.download_get), + web.head(p + r"/{id:\d+}/download", h.download_head), + web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get), + ]) + log.debug(f"Index added for {chat.id} at /{alias_id}") else: for chat_id in include_chats: chat = await client.get_entity(chat_id) - alias_id = generate_alias_id(chat) - log.debug(f"Index added for {chat.id} :: {chat.title} at /{alias_id}") - + alias_id = h.generate_alias_id(chat) + p = "/{chat:" + alias_id + "}" + routes.extend([ + web.get(p, h.index), + web.get(p + r"/logo", h.logo), + web.get(p + r"/{id:\d+}/view", h.info), + web.get(p + r"/{id:\d+}/download", h.download_get), + web.head(p + r"/{id:\d+}/download", h.download_head), + web.get(p + r"/{id:\d+}/thumbnail", h.thumbnail_get), + ]) + log.debug(f"Index added for {chat.id} at /{alias_id}") + routes.append(web.view(r'/{wildcard:.*}', h.wildcard)) app.add_routes(routes) diff --git a/app/telegram.py b/app/telegram.py index db01cf5..63be4bd 100644 --- a/app/telegram.py +++ b/app/telegram.py @@ -10,7 +10,7 @@ class Client(TelegramClient): def __init__(self, session_string, *args, **kwargs): super().__init__(StringSession(session_string), *args, **kwargs) self.log = logging.getLogger(__name__) - + async def download(self, file, file_size, offset, limit): part_size_kb = utils.get_appropriated_part_size(file_size) @@ -25,7 +25,7 @@ class Client(TelegramClient): async for chunk in self.iter_download(file, offset=first_part * part_size, request_size=part_size): if part == first_part: yield chunk[first_part_cut:] - elif part == last_part: + elif part == last_part-1: yield chunk[:last_part_cut] else: yield chunk diff --git a/app/templates/info.html b/app/templates/info.html index 8c88817..b2ffb67 100644 --- a/app/templates/info.html +++ b/app/templates/info.html @@ -1,26 +1,29 @@ {% include 'header.html' %} - +
{% if found %} {% if media %}

Download {{name}}

- -
+ +
+
+ +
{% if media.image %} {{name}} {% elif media.video %} - -
+ +
- + {% endif %} - + {% if caption_html %} - +

{{ caption_html|safe }}

@@ -87,15 +90,15 @@
{% endif %}
- + {% endif %} - +
- + {% else %}
@@ -115,15 +118,15 @@ {% endif %}
{% endif %} - + {% else %}

Ooops...

- +

{{ reason }}

{% endif %} - +
- + {% include 'footer.html' %} diff --git a/app/views.py b/app/views.py deleted file mode 100644 index 467c2b0..0000000 --- a/app/views.py +++ /dev/null @@ -1,483 +0,0 @@ -import logging -from PIL import Image, ImageDraw, ImageFont -import random -import io - -from aiohttp import web -import aiohttp_jinja2 -from jinja2 import Markup -from telethon.tl import types -from telethon.tl.custom import Message -from telethon.tl.types import User, Chat, Channel - -from .util import get_file_name, get_human_size -from .config import otg_settings, chat_ids, enable_otg - - -log = logging.getLogger(__name__) - - -class Views: - - def __init__(self, client): - self.client = client - - - async def wildcard(self, req): - raise web.HTTPFound('/') - - - @aiohttp_jinja2.template('home.html') - async def home(self, req): - if len(chat_ids) == 1: - raise web.HTTPFound(f"{chat_ids[0]['alias_id']}") - - chats = [] - for chat in chat_ids: - chats.append({ - 'page_id': chat['alias_id'], - 'name': chat['title'], - 'url': req.rel_url.path + f"/{chat['alias_id']}" - }) - return {'chats':chats, 'otg': enable_otg} - - - @aiohttp_jinja2.template('otg.html') - async def otg_view(self, req): - if not enable_otg: - raise web.HTTPFound('/') - return_data = {} - error = req.query.get('e') - if error: - return_data.update({'error': error}) - - return return_data - - - - async def dynamic_view(self, req): - if not enable_otg: - raise web.HTTPFound('/') - - rel_url = req.rel_url - include_private = otg_settings['include_private'] - include_group = otg_settings['include_group'] - include_channel = otg_settings['include_channel'] - post_data = await req.post() - raw_id = post_data.get('id') - if not raw_id: - raise web.HTTPFound('/') - - raw_id.replace('@', '') - try: - chat = await self.client.get_entity(raw_id) - except Exception as e: - log.debug(e, exc_info=True) - raise web.HTTPFound(rel_url.with_query({'e': f"No chat found with username {raw_id}"})) - - if isinstance(chat, User) and not include_private: - raise web.HTTPFound(rel_url.with_query({'e': "Indexing private chats is not supported!!"})) - elif isinstance(chat, Channel) and not include_channel: - raise web.HTTPFound(rel_url.with_query({'e': "Indexing channels is not supported!!"})) - elif isinstance(chat, Chat) and not include_group: - raise web.HTTPFound(rel_url.with_query({'e': "Indexing group chats is not supported!!"})) - - log.debug(f"chat {chat} accessed!!") - raise web.HTTPFound(f'/{chat.id}') - - - @aiohttp_jinja2.template('index.html') - async def index(self, req): - alias_id = req.match_info['chat'] - chat = [i for i in chat_ids if i['alias_id'] == alias_id] - if not chat: - if not enable_otg: - raise web.HTTPFound('/') - - try: - chat_id = int(alias_id) - chat_ = await self.client.get_entity(chat_id) - chat_name = chat_.title - except: - raise web.HTTPFound('/') - else: - chat = chat[0] - chat_id = chat['chat_id'] - chat_name = chat['title'] - log_msg = '' - try: - offset_val = int(req.query.get('page', '1')) - except: - offset_val = 1 - log_msg += f"page: {offset_val} | " - try: - search_query = req.query.get('search', '') - except: - search_query = '' - log_msg += f"search query: {search_query} | " - offset_val = 0 if offset_val <=1 else offset_val-1 - try: - kwargs = { - 'entity': chat_id, - 'limit': 20, - 'add_offset': 20*offset_val - } - if search_query: - kwargs.update({'search': search_query}) - messages = (await self.client.get_messages(**kwargs)) or [] - - except: - log.debug("failed to get messages", exc_info=True) - messages = [] - log_msg += f"found {len(messages)} results | " - log.debug(log_msg) - results = [] - for m in messages: - entry = None - if m.file and not isinstance(m.media, types.MessageMediaWebPage): - entry = dict( - file_id=m.id, - media=True, - thumbnail=f"/{alias_id}/{m.id}/thumbnail", - mime_type=m.file.mime_type, - insight = get_file_name(m), - date = str(m.date), - size=m.file.size, - human_size=get_human_size(m.file.size), - url=req.rel_url.path + f"/{m.id}/view" - ) - elif m.message: - entry = dict( - file_id=m.id, - media=False, - mime_type='text/plain', - insight = m.raw_text[:100], - date = str(m.date), - size=len(m.raw_text), - human_size=get_human_size(len(m.raw_text)), - url=req.rel_url.path + f"/{m.id}/view" - ) - if entry: - results.append(entry) - prev_page = False - next_page = False - if offset_val: - query = {'page':offset_val} - if search_query: - query.update({'search':search_query}) - prev_page = { - 'url': str(req.rel_url.with_query(query)), - 'no': offset_val - } - - if len(messages)==20: - query = {'page':offset_val+2} - if search_query: - query.update({'search':search_query}) - next_page = { - 'url': str(req.rel_url.with_query(query)), - 'no': offset_val+2 - } - - return { - 'item_list':results, - 'prev_page': prev_page, - 'cur_page' : offset_val+1, - 'next_page': next_page, - 'search': search_query, - 'name' : chat_name, - 'logo': f"/{alias_id}/logo", - 'title' : "Index of " + chat_name - } - - - @aiohttp_jinja2.template('info.html') - async def info(self, req): - file_id = int(req.match_info["id"]) - alias_id = req.match_info['chat'] - chat = [i for i in chat_ids if i['alias_id'] == alias_id] - if not chat: - if not enable_otg: - raise web.HTTPFound('/') - try: - chat_id = int(alias_id) - except: - raise web.HTTPFound('/') - else: - chat = chat[0] - chat_id = chat['chat_id'] - try: - message = await self.client.get_messages(entity=chat_id, ids=file_id) - except: - log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) - message = None - if not message or not isinstance(message, Message): - log.debug(f"no valid entry for {file_id} in {chat_id}") - return { - 'found':False, - 'reason' : "Entry you are looking for cannot be retrived!", - } - return_val = {} - reply_btns = [] - if message.reply_markup: - if isinstance(message.reply_markup, types.ReplyInlineMarkup): - for button_row in message.reply_markup.rows: - btns = [] - for button in button_row.buttons: - if isinstance(button, types.KeyboardButtonUrl): - btns.append({'url': button.url, 'text': button.text}) - reply_btns.append(btns) - if message.file and not isinstance(message.media, types.MessageMediaWebPage): - file_name = get_file_name(message) - file_size = message.file.size - human_file_size = get_human_size(file_size) - media = { - 'type':message.file.mime_type - } - if 'video/' in message.file.mime_type: - media.update({ - 'video' : True - }) - elif 'audio/' in message.file.mime_type: - media['audio'] = True - elif 'image/' in message.file.mime_type: - media['image'] = True - - if message.text: - caption = message.raw_text - else: - caption = '' - caption_html = Markup.escape(caption).__str__().replace('\n', '
') - return_val = { - 'found': True, - 'name': file_name, - 'file_id': file_id, - 'size': file_size, - 'human_size': human_file_size, - 'media': media, - 'caption_html': caption_html, - 'caption': caption, - 'title': f"Download | {file_name} | {human_file_size}", - 'reply_btns': reply_btns, - 'thumbnail': f"/{alias_id}/{file_id}/thumbnail", - 'download_url': f"/{alias_id}/{file_id}/download", - 'page_id': alias_id - } - elif message.message: - text = message.raw_text - text_html = Markup.escape(text).__str__().replace('\n', '
') - return_val = { - 'found': True, - 'media': False, - 'text': text, - 'text_html': text_html, - 'reply_btns': reply_btns, - 'page_id': alias_id - } - else: - return_val = { - 'found':False, - 'reason' : "Some kind of entry that I cannot display", - } - log.debug(f"data for {file_id} in {chat_id} returned as {return_val}") - return return_val - - - async def logo(self, req): - alias_id = req.match_info['chat'] - chat = [i for i in chat_ids if i['alias_id'] == alias_id] - if not chat: - if not enable_otg: - return web.Response(status=403, text="403: Forbiden") - try: - chat_id = int(alias_id) - except: - return web.Response(status=403, text="403: Forbiden") - else: - chat = chat[0] - chat_id = chat['chat_id'] - chat_name = "Image not available" - try: - photo = await self.client.get_profile_photos(chat_id) - except: - log.debug(f"Error in getting profile picture in {chat_id}", exc_info=True) - photo = None - if not photo: - W, H = (160, 160) - c = lambda : random.randint(0, 255) - color = tuple([c() for i in range(3)]) - im = Image.new("RGB", (W,H), color) - draw = ImageDraw.Draw(im) - w, h = draw.textsize(chat_name) - draw.text(((W-w)/2,(H-h)/2), chat_name, fill="white") - temp = io.BytesIO() - im.save(temp, "PNG") - body = temp.getvalue() - else: - photo = photo[0] - pos = -1 if req.query.get('big', None) else int(len(photo.sizes)/2) - size = self.client._get_thumb(photo.sizes, pos) - if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): - body = self.client._download_cached_photo_size(size, bytes) - else: - media = types.InputPhotoFileLocation( - id=photo.id, - access_hash=photo.access_hash, - file_reference=photo.file_reference, - thumb_size=size.type - ) - body = self.client.iter_download(media) - - r = web.Response( - status=200, - body=body, - headers={ - "Content-Type": "image/jpeg", - "Content-Disposition": 'inline; filename="logo.jpg"' - } - ) - #r.enable_chunked_encoding() - return r - - - async def download_get(self, req): - return await self.handle_request(req) - - - async def download_head(self, req): - return await self.handle_request(req, head=True) - - - async def thumbnail_get(self, req): - file_id = int(req.match_info["id"]) - alias_id = req.match_info['chat'] - chat = [i for i in chat_ids if i['alias_id'] == alias_id] - if not chat: - if not enable_otg: - return web.Response(status=403, text="403: Forbiden") - try: - chat_id = int(alias_id) - except: - return web.Response(status=403, text="403: Forbiden") - else: - chat = chat[0] - chat_id = chat['chat_id'] - try: - message = await self.client.get_messages(entity=chat_id, ids=file_id) - except: - log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) - message = None - - if not message or not message.file: - log.debug(f"no result for {file_id} in {chat_id}") - return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") - - if message.document: - media = message.document - thumbnails = media.thumbs - location = types.InputDocumentFileLocation - else: - media = message.photo - thumbnails = media.sizes - location = types.InputPhotoFileLocation - - if not thumbnails: - c = lambda : random.randint(0, 255) - color = tuple([c() for i in range(3)]) - im = Image.new("RGB", (100, 100), color) - temp = io.BytesIO() - im.save(temp, "PNG") - body = temp.getvalue() - else: - thumb_pos = int(len(thumbnails)/2) - thumbnail = self.client._get_thumb(thumbnails, thumb_pos) - if not thumbnail or isinstance(thumbnail, types.PhotoSizeEmpty): - return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") - - if isinstance(thumbnail, (types.PhotoCachedSize, types.PhotoStrippedSize)): - body = self.client._download_cached_photo_size(thumbnail, bytes) - else: - actual_file = location( - id=media.id, - access_hash=media.access_hash, - file_reference=media.file_reference, - thumb_size=thumbnail.type - ) - - body = self.client.iter_download(actual_file) - - r = web.Response( - status=200, - body=body, - headers={ - "Content-Type": "image/jpeg", - "Content-Disposition": 'inline; filename="thumbnail.jpg"' - } - ) - #r.enable_chunked_encoding() - return r - - - async def handle_request(self, req, head=False): - file_id = int(req.match_info["id"]) - alias_id = req.match_info['chat'] - chat = [i for i in chat_ids if i['alias_id'] == alias_id] - if not chat: - if not enable_otg: - return web.Response(status=403, text="403: Forbiden") - try: - chat_id = int(alias_id) - except: - return web.Response(status=403, text="403: Forbiden") - else: - chat = chat[0] - chat_id = chat['chat_id'] - - try: - message = await self.client.get_messages(entity=chat_id, ids=file_id) - except: - log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) - message = None - - if not message or not message.file: - log.debug(f"no result for {file_id} in {chat_id}") - return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") - - media = message.media - size = message.file.size - file_name = get_file_name(message) - mime_type = message.file.mime_type - - try: - offset = req.http_range.start or 0 - limit = req.http_range.stop or size - if (limit > size) or (offset < 0) or (limit < offset): - raise ValueError("range not in acceptable format") - except ValueError: - return web.Response( - status=416, - text="416: Range Not Satisfiable", - headers = { - "Content-Range": f"bytes */{size}" - } - ) - - if not head: - body = self.client.download(media, size, offset, limit) - log.info(f"Serving file in {message.id} (chat {chat_id}) ; Range: {offset} - {limit}") - else: - body = None - - headers = { - "Content-Type": mime_type, - "Content-Range": f"bytes {offset}-{limit}/{size}", - "Content-Length": str(limit - offset), - "Accept-Ranges": "bytes", - "Content-Disposition": f'attachment; filename="{file_name}"' - } - - return web.Response( - status=206 if offset else 200, - body=body, - headers=headers - ) diff --git a/app/views/__init__.py b/app/views/__init__.py new file mode 100644 index 0000000..5ed53f8 --- /dev/null +++ b/app/views/__init__.py @@ -0,0 +1,39 @@ +import random +import string + +from telethon.utils import get_display_name + +from .home_view import HomeView +from .wildcard_view import WildcardView +from .download import Download +from .index_view import IndexView +from .info_view import InfoView +from .logo_view import LogoView +from .thumbnail_view import ThumbnailView + + +class Views(HomeView, Download, + IndexView, InfoView, + LogoView, ThumbnailView, + WildcardView): + + def __init__(self, client): + self.client = client + + self.alias_ids = [] + self.chat_ids = [] + + def generate_alias_id(self, chat): + chat_id = chat.id + title = chat.title + while True: + alias_id = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(len(str(chat_id)))]) + if alias_id in self.alias_ids: + continue + self.alias_ids.append(alias_id) + self.chat_ids.append({ + 'chat_id': chat_id, + 'alias_id': alias_id, + 'title': title + }) + return alias_id diff --git a/app/views/download.py b/app/views/download.py new file mode 100644 index 0000000..700344a --- /dev/null +++ b/app/views/download.py @@ -0,0 +1,78 @@ +import logging + +from aiohttp import web + +from app.util import get_file_name +from app.config import block_downloads + + +log = logging.getLogger(__name__) + + +class Download: + + async def download_get(self, req): + return await self.handle_request(req) + + + async def download_head(self, req): + return await self.handle_request(req, head=True) + + + async def handle_request(self, req, head=False): + if block_downloads: + return web.Response(status=403, text="403: Forbiden") + + file_id = int(req.match_info["id"]) + alias_id = req.match_info['chat'] + chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0] + chat_id = chat['chat_id'] + + try: + message = await self.client.get_messages(entity=chat_id, ids=file_id) + except: + log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) + message = None + + if not message or not message.file: + log.debug(f"no result for {file_id} in {chat_id}") + return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") + + media = message.media + size = message.file.size + file_name = get_file_name(message) + mime_type = message.file.mime_type + + try: + offset = req.http_range.start or 0 + limit = req.http_range.stop or size + if (limit > size) or (offset < 0) or (limit < offset): + raise ValueError("range not in acceptable format") + except ValueError: + return web.Response( + status=416, + text="416: Range Not Satisfiable", + headers = { + "Content-Range": f"bytes */{size}" + } + ) + + if not head: + body = self.client.download(media, size, offset, limit) + log.info(f"Serving file in {message.id} (chat {chat_id}) ; Range: {offset} - {limit}") + else: + body = None + + headers = { + "Content-Type": mime_type, + "Content-Range": f"bytes {offset}-{limit}/{size}", + "Content-Length": str(limit - offset), + "Accept-Ranges": "bytes", + "Content-Disposition": f'attachment; filename="{file_name}"' + } + + return web.Response( + status=206 if offset else 200, + body=body, + headers=headers + ) diff --git a/app/views/home_view.py b/app/views/home_view.py new file mode 100644 index 0000000..79eaeda --- /dev/null +++ b/app/views/home_view.py @@ -0,0 +1,19 @@ +from aiohttp import web +import aiohttp_jinja2 + + +class HomeView: + + @aiohttp_jinja2.template('home.html') + async def home(self, req): + if len(self.chat_ids) == 1: + raise web.HTTPFound(f"{self.chat_ids[0]['alias_id']}") + + chats = [] + for chat in self.chat_ids: + chats.append({ + 'page_id': chat['alias_id'], + 'name': chat['title'], + 'url': f"/{chat['alias_id']}" + }) + return {'chats': chats} diff --git a/app/views/index_view.py b/app/views/index_view.py new file mode 100644 index 0000000..99c8057 --- /dev/null +++ b/app/views/index_view.py @@ -0,0 +1,99 @@ +import logging + +import aiohttp_jinja2 + +from telethon.tl import types + +from app.config import results_per_page +from app.util import get_file_name, get_human_size + + +log = logging.getLogger(__name__) + + +class IndexView: + + @aiohttp_jinja2.template('index.html') + async def index(self, req): + alias_id = req.match_info['chat'] + chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0] + log_msg = '' + try: + offset_val = int(req.query.get('page', '1')) + except: + offset_val = 1 + log_msg += f"page: {offset_val} | " + try: + search_query = req.query.get('search', '') + except: + search_query = '' + log_msg += f"search query: {search_query} | " + offset_val = 0 if offset_val <=1 else offset_val-1 + try: + kwargs = { + 'entity': chat['chat_id'], + 'limit': results_per_page, + 'add_offset': results_per_page*offset_val + } + if search_query: + kwargs.update({'search': search_query}) + messages = (await self.client.get_messages(**kwargs)) or [] + + except: + log.debug("failed to get messages", exc_info=True) + messages = [] + log_msg += f"found {len(messages)} results | " + log.debug(log_msg) + results = [] + for m in messages: + entry = None + if m.file and not isinstance(m.media, types.MessageMediaWebPage): + entry = dict( + file_id=m.id, + media=True, + thumbnail=f"/{alias_id}/{m.id}/thumbnail", + mime_type=m.file.mime_type, + insight = get_file_name(m), + human_size=get_human_size(m.file.size), + url=f"/{alias_id}/{m.id}/view" + ) + elif m.message: + entry = dict( + file_id=m.id, + media=False, + mime_type='text/plain', + insight = m.raw_text[:100], + url=f"/{alias_id}/{m.id}/view" + ) + if entry: + results.append(entry) + prev_page = False + next_page = False + if offset_val: + query = {'page':offset_val} + if search_query: + query.update({'search':search_query}) + prev_page = { + 'url': str(req.rel_url.with_query(query)), + 'no': offset_val + } + + if len(messages)==results_per_page: + query = {'page':offset_val+2} + if search_query: + query.update({'search':search_query}) + next_page = { + 'url': str(req.rel_url.with_query(query)), + 'no': offset_val+2 + } + + return { + 'item_list':results, + 'prev_page': prev_page, + 'cur_page' : offset_val+1, + 'next_page': next_page, + 'search': search_query, + 'name' : chat['title'], + 'logo': f"/{alias_id}/logo", + 'title' : "Index of " + chat['title'] + } diff --git a/app/views/info_view.py b/app/views/info_view.py new file mode 100644 index 0000000..63f776f --- /dev/null +++ b/app/views/info_view.py @@ -0,0 +1,90 @@ +import logging + +import aiohttp_jinja2 +from telethon.tl import types +from telethon.tl.custom import Message +from jinja2 import Markup + +from app.util import get_file_name, get_human_size + + +log = logging.getLogger(__name__) + + +class InfoView: + + @aiohttp_jinja2.template('info.html') + async def info(self, req): + file_id = int(req.match_info["id"]) + alias_id = req.match_info['chat'] + chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0] + chat_id = chat['chat_id'] + try: + message = await self.client.get_messages(entity=chat_id, ids=file_id) + except: + log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) + message = None + if not message or not isinstance(message, Message): + log.debug(f"no valid entry for {file_id} in {chat_id}") + return { + 'found':False, + 'reason' : "Resource you are looking for cannot be retrived!", + } + return_val = {} + reply_btns = [] + if message.reply_markup: + if isinstance(message.reply_markup, types.ReplyInlineMarkup): + for button_row in message.reply_markup.rows: + btns = [] + for button in button_row.buttons: + if isinstance(button, types.KeyboardButtonUrl): + btns.append({'url': button.url, 'text': button.text}) + reply_btns.append(btns) + if message.file and not isinstance(message.media, types.MessageMediaWebPage): + file_name = get_file_name(message) + human_file_size = get_human_size(message.file.size) + media = { + 'type':message.file.mime_type + } + if 'video/' in message.file.mime_type: + media['video'] : True + elif 'audio/' in message.file.mime_type: + media['audio'] = True + elif 'image/' in message.file.mime_type: + media['image'] = True + + if message.text: + caption = message.raw_text + else: + caption = '' + caption_html = Markup.escape(caption).__str__().replace('\n', '
') + return_val = { + 'found': True, + 'name': file_name, + 'file_id': file_id, + 'human_size': human_file_size, + 'media': media, + 'caption_html': caption_html, + 'title': f"Download | {file_name} | {human_file_size}", + 'reply_btns': reply_btns, + 'thumbnail': f"/{alias_id}/{file_id}/thumbnail", + 'download_url': f"/{alias_id}/{file_id}/download", + 'page_id': alias_id + } + elif message.message: + text = message.raw_text + text_html = Markup.escape(text).__str__().replace('\n', '
') + return_val = { + 'found': True, + 'media': False, + 'text_html': text_html, + 'reply_btns': reply_btns, + 'page_id': alias_id + } + else: + return_val = { + 'found':False, + 'reason' : "Some kind of resource that I cannot display", + } + log.debug(f"data for {file_id} in {chat_id} returned as {return_val}") + return return_val diff --git a/app/views/logo_view.py b/app/views/logo_view.py new file mode 100644 index 0000000..68a663f --- /dev/null +++ b/app/views/logo_view.py @@ -0,0 +1,63 @@ +import logging +from PIL import Image, ImageDraw +import random +import os + +from aiohttp import web +from telethon.tl import types + +from app.config import logo_folder + + +log = logging.getLogger(__name__) + + +class LogoView: + + async def logo(self, req): + alias_id = req.match_info['chat'] + chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0] + chat_id = chat['chat_id'] + chat_name = "Image not available" + logo_path = logo_folder.joinpath(f"{alias_id}.jpg") + if not logo_path.exists(): + try: + photo = await self.client.get_profile_photos(chat_id) + except: + log.debug(f"Error in getting profile picture in {chat_id}", exc_info=True) + photo = None + + if not photo: + W, H = (160, 160) + color = tuple([random.randint(0, 255) for i in range(3)]) + im = Image.new("RGB", (W,H), color) + draw = ImageDraw.Draw(im) + w, h = draw.textsize(chat_name) + draw.text(((W-w)/2,(H-h)/2), chat_name, fill="white") + im.save(logo_path) + else: + photo = photo[0] + pos = -1 if req.query.get('big', None) else int(len(photo.sizes)/2) + size = self.client._get_thumb(photo.sizes, pos) + if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): + await self.client._download_cached_photo_size(size, logo_path) + else: + media = types.InputPhotoFileLocation( + id=photo.id, + access_hash=photo.access_hash, + file_reference=photo.file_reference, + thumb_size=size.type + ) + await self.client.download_file(media, logo_path) + + with open(logo_path, 'rb') as fp: + body = fp.read() + + return web.Response( + status=200, + body=body, + headers={ + "Content-Type": "image/jpeg", + "Content-Disposition": 'inline; filename="logo.jpg"' + } + ) diff --git a/app/views/thumbnail_view.py b/app/views/thumbnail_view.py new file mode 100644 index 0000000..adb53bc --- /dev/null +++ b/app/views/thumbnail_view.py @@ -0,0 +1,71 @@ +import logging +from PIL import Image +import random +import io + +from aiohttp import web +from telethon.tl import types + + +log = logging.getLogger(__name__) + + +class ThumbnailView: + + async def thumbnail_get(self, req): + file_id = int(req.match_info["id"]) + alias_id = req.match_info['chat'] + chat = [i for i in self.chat_ids if i['alias_id'] == alias_id][0] + chat_id = chat['chat_id'] + try: + message = await self.client.get_messages(entity=chat_id, ids=file_id) + except: + log.debug(f"Error in getting message {file_id} in {chat_id}", exc_info=True) + message = None + + if not message or not message.file: + log.debug(f"no result for {file_id} in {chat_id}") + return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") + + if message.document: + media = message.document + thumbnails = media.thumbs + location = types.InputDocumentFileLocation + else: + media = message.photo + thumbnails = media.sizes + location = types.InputPhotoFileLocation + + if not thumbnails: + color = tuple([random.randint(0, 255) for i in range(3)]) + im = Image.new("RGB", (100, 100), color) + temp = io.BytesIO() + im.save(temp, "PNG") + body = temp.getvalue() + else: + thumb_pos = int(len(thumbnails)/2) + thumbnail = self.client._get_thumb(thumbnails, thumb_pos) + if not thumbnail or isinstance(thumbnail, types.PhotoSizeEmpty): + return web.Response(status=410, text="410: Gone. Access to the target resource is no longer available!") + + if isinstance(thumbnail, (types.PhotoCachedSize, types.PhotoStrippedSize)): + body = self.client._download_cached_photo_size(thumbnail, bytes) + else: + actual_file = location( + id=media.id, + access_hash=media.access_hash, + file_reference=media.file_reference, + thumb_size=thumbnail.type + ) + + body = self.client.iter_download(actual_file) + + r = web.Response( + status=200, + body=body, + headers={ + "Content-Type": "image/jpeg", + "Content-Disposition": 'inline; filename="thumbnail.jpg"' + } + ) + return r diff --git a/app/views/wildcard_view.py b/app/views/wildcard_view.py new file mode 100644 index 0000000..164a2a1 --- /dev/null +++ b/app/views/wildcard_view.py @@ -0,0 +1,7 @@ +from aiohttp import web + + +class WildcardView: + + async def wildcard(self, req): + raise web.HTTPFound('/')