2026-01-09 22:56:54 +00:00
|
|
|
import sys
|
2026-03-05 17:55:33 +00:00
|
|
|
import requests
|
2026-03-07 04:40:45 +00:00
|
|
|
import json
|
2026-01-09 22:56:54 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
|
|
2026-03-05 17:55:33 +00:00
|
|
|
from flask import Flask, render_template, request, redirect, flash, abort
|
2026-03-07 04:40:45 +00:00
|
|
|
from markupsafe import Markup
|
|
|
|
|
from textwrap import dedent
|
2026-01-09 23:45:03 +00:00
|
|
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
2026-01-30 21:56:18 +00:00
|
|
|
from flask_wtf.csrf import CSRFProtect
|
2026-01-09 22:56:54 +00:00
|
|
|
import hashlib
|
|
|
|
|
import hmac
|
|
|
|
|
|
2026-03-05 17:55:33 +00:00
|
|
|
from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN, MASTODON_OAUTH_REDIRECT_URI
|
|
|
|
|
from db.queries import user_get, user_mastodon_user_set
|
2026-03-07 04:40:45 +00:00
|
|
|
from web.api import api
|
|
|
|
|
|
|
|
|
|
DEBUG_VITE_PREFIX = '/v/'
|
|
|
|
|
PROD_VITE_PREFIX = '/static/'
|
2026-01-09 22:56:54 +00:00
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
app.secret_key = FLASK_SECRET_KEY
|
|
|
|
|
|
2026-01-09 23:45:03 +00:00
|
|
|
login_manager = LoginManager()
|
|
|
|
|
login_manager.init_app(app)
|
|
|
|
|
login_manager.login_view = "index"
|
|
|
|
|
|
2026-01-30 21:56:18 +00:00
|
|
|
csrf = CSRFProtect()
|
|
|
|
|
csrf.init_app(app)
|
|
|
|
|
|
2026-01-09 23:45:03 +00:00
|
|
|
class FlaskUser(UserMixin):
|
|
|
|
|
def __init__(self, db_user):
|
|
|
|
|
self.db_user = db_user
|
|
|
|
|
|
|
|
|
|
def get_id(self):
|
|
|
|
|
return self.db_user.telegram_username
|
|
|
|
|
|
2026-03-07 16:55:33 +00:00
|
|
|
try:
|
|
|
|
|
with open("web/static/.vite/manifest.json") as f:
|
|
|
|
|
vite_manifest = json.load(f)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
vite_manifest = None
|
2026-03-07 04:40:45 +00:00
|
|
|
|
|
|
|
|
@app.context_processor
|
|
|
|
|
def inject_vite_entry():
|
|
|
|
|
def vite_entry(file):
|
2026-03-07 16:55:33 +00:00
|
|
|
if vite_manifest is None:
|
2026-03-07 04:40:45 +00:00
|
|
|
return Markup(
|
|
|
|
|
dedent(f"""
|
|
|
|
|
<script type="module" src="{DEBUG_VITE_PREFIX}@vite/client"></script>
|
|
|
|
|
<script type="module" src="{DEBUG_VITE_PREFIX}{file}"></script>
|
|
|
|
|
""").strip())
|
2026-03-07 16:55:33 +00:00
|
|
|
elif file in vite_manifest:
|
2026-03-07 04:40:45 +00:00
|
|
|
src = vite_manifest[file]['file']
|
|
|
|
|
result = f'<script type="module" src="{PROD_VITE_PREFIX}{src}"></script>'
|
|
|
|
|
if('css' in vite_manifest[file]):
|
|
|
|
|
for css in vite_manifest[file]['css']:
|
|
|
|
|
result += f'<link href="{PROD_VITE_PREFIX}{css}" rel="stylesheet" />'
|
|
|
|
|
return Markup(
|
|
|
|
|
dedent(result.strip()))
|
|
|
|
|
return dict(vite_entry=vite_entry)
|
|
|
|
|
|
2026-01-09 23:45:03 +00:00
|
|
|
@login_manager.user_loader
|
|
|
|
|
def load_user(user_id):
|
|
|
|
|
try:
|
|
|
|
|
db_user = user_get(user_id)
|
|
|
|
|
return FlaskUser(db_user)
|
|
|
|
|
except:
|
|
|
|
|
return None
|
|
|
|
|
|
2026-01-10 03:44:55 +00:00
|
|
|
app.register_blueprint(api, url_prefix='/api')
|
|
|
|
|
|
2026-01-09 22:56:54 +00:00
|
|
|
@app.route('/')
|
|
|
|
|
def index():
|
2026-01-09 23:45:03 +00:00
|
|
|
if not current_user.is_authenticated:
|
|
|
|
|
data = {'bot_name': TELEGRAM_BOT_NAME, 'bot_damin': TELEGRAM_BOT_DOMAIN}
|
|
|
|
|
return render_template('index.html', data = data)
|
|
|
|
|
else:
|
2026-03-07 04:40:45 +00:00
|
|
|
print('Redirect to dashboard')
|
2026-01-10 03:44:55 +00:00
|
|
|
return redirect('/dashboard/')
|
2026-01-09 22:56:54 +00:00
|
|
|
|
2026-03-07 04:40:45 +00:00
|
|
|
@app.route('/profile/', defaults={'path': ''})
|
2026-01-10 03:44:55 +00:00
|
|
|
@app.route('/dashboard/', defaults={'path': ''})
|
2026-03-04 21:14:18 +00:00
|
|
|
@app.route('/orders/<path:path>')
|
2026-01-10 03:44:55 +00:00
|
|
|
@app.route('/dashboard/<path:path>')
|
2026-01-09 23:45:03 +00:00
|
|
|
@login_required
|
2026-01-10 03:44:55 +00:00
|
|
|
def dashboard(path):
|
2026-01-09 22:56:54 +00:00
|
|
|
return render_template('dashboard.html')
|
|
|
|
|
|
2026-01-09 23:45:03 +00:00
|
|
|
@app.route('/logout')
|
|
|
|
|
def logout():
|
|
|
|
|
logout_user()
|
|
|
|
|
return redirect('/')
|
|
|
|
|
|
2026-01-09 22:56:54 +00:00
|
|
|
def string_generator(data_incoming):
|
|
|
|
|
data = data_incoming.copy()
|
|
|
|
|
del data['hash']
|
|
|
|
|
keys = sorted(data.keys())
|
|
|
|
|
string_arr = []
|
|
|
|
|
for key in keys:
|
|
|
|
|
string_arr.append(key + '=' + data[key])
|
|
|
|
|
string_cat = '\n'.join(string_arr)
|
|
|
|
|
return string_cat
|
|
|
|
|
|
2026-03-05 17:55:33 +00:00
|
|
|
@app.route('/oauth')
|
|
|
|
|
@login_required
|
|
|
|
|
def mastodon_oauth():
|
|
|
|
|
code = request.args['code']
|
|
|
|
|
server = current_user.db_user.mastodon_server
|
|
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
|
'grant_type': 'authorization_code',
|
|
|
|
|
'client_id': server.client_id,
|
|
|
|
|
'client_secret': server.client_secret,
|
|
|
|
|
'redirect_uri': MASTODON_OAUTH_REDIRECT_URI,
|
|
|
|
|
'code': code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token_request = requests.post(f"https://{server.name}/oauth/token", data=payload)
|
|
|
|
|
|
|
|
|
|
if not token_request.ok:
|
|
|
|
|
import pdb; pdb.set_trace()
|
|
|
|
|
abort(500)
|
|
|
|
|
|
|
|
|
|
access_token = token_request.json()['access_token']
|
|
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
|
"Authorization": f"Bearer {access_token}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
verify_request = requests.get(f"https://{server.name}/api/v1/accounts/verify_credentials", headers=headers)
|
|
|
|
|
|
|
|
|
|
if not verify_request.ok:
|
|
|
|
|
import pdb; pdb.set_trace()
|
|
|
|
|
abort(500)
|
|
|
|
|
|
|
|
|
|
username = verify_request.json()['username']
|
|
|
|
|
|
|
|
|
|
user_mastodon_user_set(current_user.db_user.id, username, access_token)
|
|
|
|
|
|
|
|
|
|
return redirect('/profile')
|
|
|
|
|
|
2026-01-09 22:56:54 +00:00
|
|
|
@app.route('/login')
|
|
|
|
|
def login():
|
|
|
|
|
tg_data = {
|
|
|
|
|
"id": request.args.get("id", None),
|
|
|
|
|
"first_name": request.args.get('first_name', None),
|
|
|
|
|
"last_name": request.args.get('last_name', None),
|
|
|
|
|
"username": request.args.get("username", None),
|
|
|
|
|
"photo_url": request.args.get("photo_url", None),
|
|
|
|
|
"auth_date": request.args.get('auth_date', None),
|
|
|
|
|
"hash": request.args.get("hash", None)
|
|
|
|
|
}
|
|
|
|
|
data_check_string = string_generator(tg_data)
|
|
|
|
|
secret_key = hashlib.sha256(TELEGRAM_API_TOKEN.encode('utf-8')).digest()
|
|
|
|
|
secret_key_bytes = secret_key
|
|
|
|
|
data_check_string_bytes = bytes(data_check_string, 'utf-8')
|
|
|
|
|
hmac_string = hmac.new(secret_key_bytes, data_check_string_bytes, hashlib.sha256).hexdigest()
|
|
|
|
|
if hmac_string == tg_data['hash']:
|
2026-01-09 23:45:03 +00:00
|
|
|
try:
|
|
|
|
|
db_user = user_get(tg_data['username'])
|
2026-01-30 20:58:37 +00:00
|
|
|
db_user.telegram_photo_url = request.args.get("photo_url")
|
|
|
|
|
db_user.save()
|
|
|
|
|
|
2026-01-09 23:45:03 +00:00
|
|
|
login_user(FlaskUser(db_user))
|
|
|
|
|
except:
|
|
|
|
|
flash("Login failed. Please try again.")
|
|
|
|
|
return redirect('/')
|
|
|
|
|
else:
|
|
|
|
|
flash("Login failed. Please try again.")
|
|
|
|
|
return redirect('/')
|
|
|
|
|
|
|
|
|
|
|
2026-01-10 03:44:55 +00:00
|
|
|
return redirect('/dashboard/')
|
2026-01-09 22:56:54 +00:00
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
app.run(host='0.0.0.0', debug=True, port=8080)
|