Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: telegram mini-app #282

Merged
merged 3 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,7 @@ pydrive/*
.idea
data/json/subjs.json
**/.DS_Store

# Webapp
node_modules/
.parcel-cache/
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
from module.shared import config_map
from module.utils.multi_lang_utils import load_translations, get_regex_multi_lang
from module.debug import error_handler, log_message

from webapp.app import app
import uvicorn

def add_commands(up: Updater) -> None:
"""Adds the list of commands with their description to the bot
Expand Down Expand Up @@ -195,6 +196,7 @@ def main() -> None:
add_jobs(updater.dispatcher)

updater.start_polling()
uvicorn.run(app, host="0.0.0.0", port=8000)
updater.idle()


Expand Down
88 changes: 22 additions & 66 deletions module/commands/gdrive.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,12 @@
"""/drive command"""
import os

import yaml
from pydrive2.auth import AuthError, GoogleAuth
from pydrive2.drive import GoogleDrive
from pydrive2.files import GoogleDriveFile
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, Bot
from telegram.ext import CallbackContext
from module.shared import check_log
from module.debug import log_error
from module.data.vars import TEXT_IDS, PLACE_HOLDER
from module.utils.multi_lang_utils import get_locale

gdrive_interface = None

with open('config/settings.yaml', 'r', encoding='utf-8') as yaml_config:
config_map = yaml.load(yaml_config, Loader=yaml.SafeLoader)


def get_gdrive_interface() -> GoogleDrive:
global gdrive_interface

if gdrive_interface is None:
# gauth uses all the client_config of settings.yaml
gauth = GoogleAuth(settings_file="config/settings.yaml")
gauth.CommandLineAuth()
gdrive_interface = GoogleDrive(gauth)

return gdrive_interface
from module.utils.drive_utils import drive_utils


def drive(update: Update, context: CallbackContext) -> None:
Expand All @@ -39,7 +18,6 @@ def drive(update: Update, context: CallbackContext) -> None:
context: context passed by the handler
"""
check_log(update, "drive")
gdrive: GoogleDrive = get_gdrive_interface()
chat_id: int = update.message.chat_id
locale: str = update.message.from_user.language_code
if chat_id < 0:
Expand All @@ -48,24 +26,20 @@ def drive(update: Update, context: CallbackContext) -> None:
)
return

try:
file_list = gdrive.ListFile(
{
'q': f"'{config_map['drive_folder_id']}' in parents and trashed=false",
'orderBy': 'folder,title',
}
).GetList()

except AuthError as err:
log_error(header="drive", error=err)

# keyboard that allows the user to navigate the folder
keyboard = get_files_keyboard(file_list, row_len=3)
context.bot.sendMessage(
chat_id=chat_id,
text=get_locale(locale, TEXT_IDS.DRIVE_HEADER_TEXT_ID),
reply_markup=InlineKeyboardMarkup(keyboard),
)
file_list = drive_utils.list_files()
if file_list:
# keyboard that allows the user to navigate the folder
keyboard = get_files_keyboard(file_list, row_len=3)
context.bot.sendMessage(
chat_id=chat_id,
text=get_locale(locale, TEXT_IDS.DRIVE_HEADER_TEXT_ID),
reply_markup=InlineKeyboardMarkup(keyboard),
)
else:
context.bot.sendMessage(
chat_id=chat_id,
text=get_locale(locale, TEXT_IDS.DRIVE_ERROR_DEVS_TEXT_ID),
)


def drive_handler(update: Update, context: CallbackContext) -> None:
Expand All @@ -78,28 +52,17 @@ def drive_handler(update: Update, context: CallbackContext) -> None:
"""
bot: Bot = context.bot

gdrive: GoogleDrive = get_gdrive_interface()

query_data: str = update.callback_query.data.replace("drive_file_", "")
chat_id: int = update.callback_query.from_user.id
message_id: int = update.callback_query.message.message_id
locale: str = update.callback_query.from_user.language_code
fetched_file: GoogleDriveFile = gdrive.CreateFile({'id': query_data})
fetched_file: GoogleDriveFile = drive_utils.get_file(query_data)

# the user clicked on a folder
if fetched_file['mimeType'] == "application/vnd.google-apps.folder":
try:
istance_file = gdrive.ListFile(
{
'q': f"'{fetched_file['id']}' in parents and trashed=false",
'orderBy': 'folder,title',
}
)
file_list = istance_file.GetList()
file_list = drive_utils.list_files(fetched_file['id'])

# pylint: disable=broad-except
except Exception as e:
log_error(header="drive_handler", error=e)
if file_list is None:
bot.editMessageText(
chat_id=chat_id,
message_id=message_id,
Expand Down Expand Up @@ -139,19 +102,12 @@ def drive_handler(update: Update, context: CallbackContext) -> None:

else: # the user clicked on a file
try:
file_d = gdrive.CreateFile({'id': fetched_file['id']})

file_d = drive_utils.get_file(fetched_file['id'])
if int(file_d['fileSize']) < 5e7:

file_path = f"file/{fetched_file['title']}"
file_d.GetContentFile(file_path)

f = file_d.GetContentIOBuffer()
f.name = fetched_file['title']
bot.sendChatAction(chat_id=chat_id, action="UPLOAD_DOCUMENT")

with open(file_path, 'rb') as f:
bot.sendDocument(chat_id=chat_id, document=f)

os.remove(file_path)
bot.sendDocument(chat_id=chat_id, document=f)

else:
bot.sendMessage(
Expand Down
43 changes: 43 additions & 0 deletions module/utils/drive_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import yaml
from typing import Optional
from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive
from pydrive2.files import GoogleDriveFileList, GoogleDriveFile
from module.debug import log_error


class DriveUtils:
def __init__(self):
self._gdrive = None
with open('config/settings.yaml', 'r', encoding='utf-8') as yaml_config:
self.config_map = yaml.load(yaml_config, Loader=yaml.SafeLoader)

@property
def gdrive(self) -> GoogleDrive:
'Returns the active drive.GoogleDrive instance.'
if self._gdrive is None:
# gauth uses all the client_config of settings.yaml
gauth = GoogleAuth(settings_file="./config/settings.yaml")
gauth.CommandLineAuth()
self._gdrive = GoogleDrive(gauth)
return self._gdrive

def list_files(self, folder_id: Optional[str] = None) -> Optional[GoogleDriveFileList]:
'Returns a list of files or folders in the given directory'
folder_id = folder_id or self.config_map['drive_folder_id']
try:
return self.gdrive.ListFile({
'q': f"'{folder_id}' in parents and trashed=false",
'orderBy': 'folder,title',
}).GetList()
# pylint: disable=broad-except
except Exception as e:
log_error(header="drive_handler", error=e)
return None

def get_file(self, file_id: str) -> GoogleDriveFile:
'Shorthand for self.gdrive.CreateFile'
return self.gdrive.CreateFile({'id': file_id})


drive_utils = DriveUtils()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
numpy==1.26.4
python-telegram-bot==13.8.1
PyDrive2==1.10.0
requests==2.26.0
Expand Down
5 changes: 5 additions & 0 deletions webapp/.postcssrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"plugins": {
"tailwindcss": {}
}
}
48 changes: 48 additions & 0 deletions webapp/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
from module.utils.drive_utils import drive_utils


app = FastAPI()

@app.get('/favicon.ico')
def favicon():
'Serve the website favicon'
return FileResponse('webapp/static/assets/logo.ico')

@app.get('/drive/folder')
def _(folder_id: str):
'Returns content of a folder in the DMI Drive.'
files = drive_utils.list_files(folder_id) or []
keys = 'id', 'title', 'mimeType'
response = JSONResponse([{key: file[key] for key in keys} for file in files])
response.headers['Access-Control-Allow-Origin'] = '*'
return response

@app.get('/drive/file')
def _(file_id: str):
'Returns content of a file in the DMI Drive.'
file = drive_utils.get_file(file_id)
if not file:
return JSONResponse({'error': 'File not found.'}, status_code = 204)
content = file.GetContentIOBuffer()
return StreamingResponse(
stream(content),
media_type = file['mimeType'],
headers = {
# delivering it as a download
'Content-Disposition': f"attachment; filename=\"{file['title']}\"",
# making it accessible from web browsers
'Access-Control-Allow-Origin': '*'
}
)

def stream(content):
TaToTanWeb marked this conversation as resolved.
Show resolved Hide resolved
chunk = True
while chunk:
chunk = content.read()
if chunk:
yield chunk

app.mount('/', StaticFiles(directory = 'webapp/dist/', html = True), name = 'dist')
48 changes: 48 additions & 0 deletions webapp/drive/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>DMI Drive</title>
<meta name = "viewport" content = "width=device-width, initial-scale=1" />
<link rel = "stylesheet" href = "static/style.css">
<script src = "https://telegram.org/js/telegram-web-app.js"></script>
<link href='https://unpkg.com/[email protected]/icons/css/folder.css' rel='stylesheet'>
<script type = "module" src = "static/scripts/index.ts"></script>
<script type = "module" src = "static/scripts/back.ts"></script>
</head>
<body>
<header class = "w-full mt-10">
<div class = "w-full flex flex-row justify-center">
<img width = "100" src = "https://upload.wikimedia.org/wikipedia/commons/1/12/Google_Drive_icon_%282020%29.svg">
<!-- <img width = "150" src = "../static/assets/logo.png"> -->
</div>
</header>
<div class = "w-full flex flex-col items-center justify-center">
<folders-div id = "folders-list">
<drive-back-button class = "folder hidden" id = "back-button"></drive-back-button>
<!-- <folder id = "folder" class = "bg-neutral-200 w-full h-10 p-6 flex flex-row items-center shadow-md hover:shadow-lg transition-shadow cursor-pointer rounded-md">
<i class = "mr-5 gg-folder text-neutral-500"></i>
<folder-name class = "font-bold text-neutral-700">Informatica</folder-name>
</folder> -->
</folders-div>
<files-div id = "files-list">
<!-- <file id = "file" class = "bg-neutral-300 flex flex-col p-5 shadow-md hover:shadow-lg transition-shadow cursor-pointer rounded-md">
<img class = "w-32 rounded-md rounded-bl-[28px]" src = "https://telegra.ph/file/cc429f1e5d235f54a4500.png">
<file-name class = "mt-5 font-bold text-neutral-900">Appunti.pdf</file-name>
</file> -->
</files-div>
</div>
</body>
<!-- <pwa>
<meta name = "theme-color" content = "#ffffff">
<link rel = "manifest" href = "static/web/manifest.json">
<link sizes = "500x500" rel = "apple-touch-icon" href = "static/assets/logo.png">

<script type = "module">
document.addEventListener('DOMContentLoaded', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(new URL('static/web/sw.js', import.meta.url), {scope: '/'});
}
});
</script>
</pwa> -->
</html>
33 changes: 33 additions & 0 deletions webapp/drive/static/scripts/back.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Folder, FOLDERMIME } from "./folder";
import { update_content } from "./update";

export class BackButton extends Folder {
foldersHistory = new Array<string>();

connectedCallback() {
TaToTanWeb marked this conversation as resolved.
Show resolved Hide resolved
this.update({
title: '..',
id: 'back',
mimeType: FOLDERMIME
})
}

onFolderChange(folderId: string) {
TaToTanWeb marked this conversation as resolved.
Show resolved Hide resolved
this.foldersHistory.push(folderId);
if (folderId != '') {
this.show();
} else {
this.hide();
}
}

override onClick(): void {
const previousFolderId = this.foldersHistory[this.foldersHistory.length - 2];
// Removing the last folder id in the history
this.foldersHistory.splice(this.foldersHistory.length - 2);
// Showing content of the previous folder
update_content(previousFolderId);
}
}

window.customElements.define('drive-back-button', BackButton);
Loading
Loading