dfdsfsafdsfsdfasdsf

This commit is contained in:
drel
2025-07-26 15:23:11 +03:00
parent 2b41b6eba8
commit c55a0a5b18
14 changed files with 846 additions and 0 deletions

BIN
ARIAL.TTF Normal file

Binary file not shown.

9
LICENSE Normal file
View File

@@ -0,0 +1,9 @@
Drel's Shitty Open Source License (DSOS License)
=================================================
Whatever it is, it is completely free and cannot be sold even in a modified state, but it can be sold if it's used as a base for other project*.
Anyone is free to copy, modify, publish and use this project* commercially or non-commercially.
Anyone is NOT required to credit the developer and contributers of this project*. (But it's allowed to do it)
* It can be software or whatever else, it can be any type of project.

498
main.py Normal file
View File

@@ -0,0 +1,498 @@
from fastapi import FastAPI, Request, Form, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
import uuid
import httpx
import json
import threading
import os
import time
from PIL import Image
import io
from PIL import Image, ImageDraw, ImageFont
import textwrap
from urllib.parse import urlparse
app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")
STORAGE_FILE = "storage.json"
os.makedirs("static/images", exist_ok=True)
FONT_PATH = "arial.ttf" # Загрузите шрифт и положите в корень
LINE_SPACING = 10 # Больше пространства между строками
FONT_SIZE = 15 # Увеличить размер шрифта
TEXT_COLOR = (0, 0, 0) # Чёрный текст
BG_COLOR = (255, 255, 255) # Белый фон
# Создаём дефолтный текст если шрифт не найден
print('тест на файлик ноу нейм')
if not os.path.exists("static/text/no_name.gif"):
print('нету ураа')
img = Image.new("RGB", (100, 20), BG_COLOR)
draw = ImageDraw.Draw(img)
draw.text((5, 5), "", fill=TEXT_COLOR)
img.save("static/text/no_name.gif", "GIF")
print('наверно всё')
def load_storage():
try:
with open(STORAGE_FILE, "r") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {}
def save_storage(data):
with open(STORAGE_FILE, "w") as f:
json.dump(data, f)
async def process_avatar(image_url: str, user_id: int):
try:
async with httpx.AsyncClient(follow_redirects=True) as client:
response = await client.get(image_url)
response.raise_for_status()
# Извлекаем реальный ID пользователя из URL инстанса
parsed_url = urlparse(image_url)
filename = f"{user_id}.gif"
filepath = f"static/images/{filename}"
img = Image.open(io.BytesIO(response.content))
img = img.resize((128, 128)).convert("RGB")
img.save(filepath, "GIF")
return f"/static/images/{filename}"
except Exception as e:
print(f"Avatar error: {str(e)}")
# Создаём дефолтную аватарку если её нет
default_path = "static/images/default.gif"
if not os.path.exists(default_path):
img = Image.new("RGB", (128, 128), color="gray")
img.save(default_path, "GIF")
return default_path
def cleanup_file(file_path):
def delete_file():
try:
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleted {file_path}")
except Exception as e:
print(f"Error deleting {file_path}: {str(e)}")
# Запускаем удаление через 5 секунд в отдельном потоке
timer = threading.Timer(5.0, delete_file)
timer.start()
def render_text(text: str, max_width: int = 480) -> str:
if isinstance(text, int): # Добавляем обработку чисел
text = str(text)
try:
text = translit(text, 'ru', reversed=True)
except:
pass
if not text.strip():
return "/static/text/no_name.gif"
try:
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
except IOError:
font = ImageFont.load_default()
# Новый метод расчёта размеров
def get_text_size(fnt, txt):
bbox = fnt.getbbox(txt)
return (bbox[2] - bbox[0], bbox[3] - bbox[1])
# Разбиваем текст на строки
wrapper = textwrap.TextWrapper(width=max_width // (FONT_SIZE))
lines = wrapper.wrap(text)
# Рассчитываем размер изображения
line_height = get_text_size(font, "A")[1] + LINE_SPACING # Добавляем spacing к высоте линии
img_width = max(get_text_size(font, line)[0] for line in lines) + 10
img_height = (line_height * len(lines)) + 10 # Учитываем суммарный spacing
# Создаём изображение
img = Image.new("RGB", (img_width, img_height), BG_COLOR)
draw = ImageDraw.Draw(img)
# Рендерим текст
y = 5
for line in lines:
draw.text((5, y), line, font=font, fill=TEXT_COLOR)
y += line_height
# Сохраняем в static/text
os.makedirs("static/text", exist_ok=True)
filename = f"{hash(text)}.gif"
img_path = f"static/text/{filename}"
img.save(img_path, "GIF")
full_path = os.path.abspath(img_path)
cleanup_file(full_path) # Добавляем в очередь на удаление
return f"/{img_path}"
def render_separator():
separator = "-" * 32 # 40 дефисов
return render_text(separator)
templates.env.globals["render_text"] = render_text
@app.get("/", response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/auth")
async def authenticate(
request: Request,
instance: str = Form(...),
username: str = Form(...),
password: str = Form(...),
):
if not all([instance, username, password]):
return RedirectResponse(url="/?error=1", status_code=303)
instance = instance if instance.startswith(("http://", "https://")) else f"http://{instance}"
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{instance.rstrip('/')}/token",
params={
"username": username,
"password": password,
"grant_type": "password",
"client_name": "ovk4webtv"
}
)
response.raise_for_status()
token_data = response.json()
except Exception as e:
return RedirectResponse(url="/?error=2", status_code=303)
storage = load_storage()
device_uuid = str(uuid.uuid4())
storage[device_uuid] = {
"token": token_data["access_token"],
"instance": instance.rstrip('/'), # Сохраняем нормализованный URL
"user_id": token_data["user_id"]
}
save_storage(storage)
response = RedirectResponse(url="/profile", status_code=303)
response.set_cookie(key="webtv_uuid", value=device_uuid)
return response
@app.get("/profile", response_class=HTMLResponse)
async def profile_page(request: Request):
device_uuid = request.cookies.get("webtv_uuid")
storage = load_storage()
if not device_uuid or device_uuid not in storage:
return RedirectResponse(url="/", status_code=303)
user_data = storage[device_uuid]
try:
async with httpx.AsyncClient() as client:
# Получаем базовую информацию профиля
profile_response = await client.get(
f"{user_data['instance'].rstrip('/')}/method/Account.getProfileInfo",
params={"access_token": user_data["token"]}
)
profile_response.raise_for_status()
profile_data = profile_response.json()["response"]
# Получаем полную информацию о пользователе
users_response = await client.get(
f"{user_data['instance'].rstrip('/')}/method/users.get",
params={
"access_token": user_data["token"],
"user_ids": profile_data["id"],
"fields": "photo_200"
}
)
users_response.raise_for_status()
user_info = users_response.json()["response"][0]
except Exception as e:
print(f"Profile error: {str(e)}")
return RedirectResponse(url="/?error=3", status_code=303)
# Используем ID из профиля вместо user_id из токена
avatar_url = await process_avatar(user_info["photo_200"], user_info["id"])
return templates.TemplateResponse("profile.html", {
"request": request,
"first_name_img": render_text(profile_data.get("first_name", "")),
"last_name_img": render_text(profile_data.get("last_name", "")),
"user_id": user_info["id"],
"avatar": avatar_url
})
# Остальные обработчики остаются без изменений
@app.get("/token", response_class=HTMLResponse)
async def show_token(request: Request):
device_uuid = request.cookies.get("webtv_uuid")
if not device_uuid or device_uuid not in tokens:
raise HTTPException(status_code=400, detail="Сессия не найдена")
return templates.TemplateResponse(
"token.html",
{
"request": request,
"token": tokens[device_uuid],
"uuid": device_uuid
}
)
@app.get("/feed", response_class=HTMLResponse)
async def news_feed(
request: Request,
page: int = 0,
global_feed: bool = False
):
device_uuid = request.cookies.get("webtv_uuid")
storage = load_storage()
# Проверка авторизации
if not device_uuid or device_uuid not in storage:
return RedirectResponse(url="/", status_code=303)
user_data = storage[device_uuid]
try:
# Получаем ленту новостей
method = "newsfeed.getGlobal" if global_feed else "newsfeed.get"
async with httpx.AsyncClient() as client:
feed_response = await client.get(
f"{user_data['instance']}/method/{method}",
params={
"access_token": user_data["token"],
"count": 5,
"offset": page * 5
}
)
feed_response.raise_for_status()
feed_data = feed_response.json()["response"]
except Exception as e:
print(f"Feed error: {str(e)}")
return RedirectResponse(url="/?error=4", status_code=303)
# Собираем ID пользователей
user_ids = set()
for item in feed_data["items"]:
user_ids.add(item["from_id"])
user_ids.add(item["owner_id"])
# Получаем информацию о пользователях
users_info = {}
try:
async with httpx.AsyncClient() as client:
users_response = await client.get(
f"{user_data['instance']}/method/users.get",
params={
"access_token": user_data["token"],
"user_ids": ",".join(map(str, user_ids)),
"fields": "first_name,last_name"
}
)
users_response.raise_for_status()
for user in users_response.json()["response"]:
users_info[user["id"]] = user
except Exception as e:
print(f"Users info error: {str(e)}")
return RedirectResponse(url="/?error=5", status_code=303)
# Формируем данные для отображения
processed_posts = []
for idx, post in enumerate(feed_data["items"]): # Добавлен enumerate
from_user = users_info.get(post["from_id"])
owner_user = users_info.get(post["owner_id"])
if not from_user or not owner_user:
continue
post_text = post.get("text", "")
if post["from_id"] == post["owner_id"]:
author_text = f"{from_user['first_name']} {from_user['last_name']} wrote:"
else:
author_text = (f"{from_user['first_name']} {from_user['last_name']} "
f"posted on {owner_user['first_name']} "
f"{owner_user['last_name']}'s wall:")
processed_posts.append({
"author": render_text(author_text),
"text": render_text(post_text),
"has_attachments": len(post.get("attachments", [])) > 0,
"likes": post.get("likes", {}).get("count", 0), # Добавляем количество лайков
"separator": render_separator() if idx < len(feed_data["items"]) - 1 else None
})
return templates.TemplateResponse("feed.html", {
"request": request,
"posts": processed_posts,
"current_page": page,
"has_next": "next_from" in feed_data,
"global_feed": global_feed
})
@app.get("/feed_global", response_class=HTMLResponse)
async def global_news_feed(
request: Request,
page: int = 0,
global_feed: bool = False
):
device_uuid = request.cookies.get("webtv_uuid")
storage = load_storage()
# Проверка авторизации
if not device_uuid or device_uuid not in storage:
return RedirectResponse(url="/", status_code=303)
user_data = storage[device_uuid]
try:
# Получаем ленту новостей
method = "newsfeed.getGlobal"
async with httpx.AsyncClient() as client:
feed_response = await client.get(
f"{user_data['instance']}/method/{method}",
params={
"access_token": user_data["token"],
"count": 5,
"offset": page * 5
}
)
feed_response.raise_for_status()
feed_data = feed_response.json()["response"]
except Exception as e:
print(f"Feed error: {str(e)}")
return RedirectResponse(url="/?error=4", status_code=303)
# Собираем ID пользователей
user_ids = set()
for item in feed_data["items"]:
user_ids.add(item["from_id"])
user_ids.add(item["owner_id"])
# Получаем информацию о пользователях
users_info = {}
try:
async with httpx.AsyncClient() as client:
users_response = await client.get(
f"{user_data['instance']}/method/users.get",
params={
"access_token": user_data["token"],
"user_ids": ",".join(map(str, user_ids)),
"fields": "first_name,last_name"
}
)
users_response.raise_for_status()
for user in users_response.json()["response"]:
users_info[user["id"]] = user
except Exception as e:
print(f"Users info error: {str(e)}")
return RedirectResponse(url="/?error=5", status_code=303)
# Формируем данные для отображения
processed_posts = []
for idx, post in enumerate(feed_data["items"]): # Добавлен enumerate
from_user = users_info.get(post["from_id"])
owner_user = users_info.get(post["owner_id"])
if not from_user or not owner_user:
continue
post_text = post.get("text", "")
if post["from_id"] == post["owner_id"]:
author_text = f"{from_user['first_name']} {from_user['last_name']} wrote:"
else:
author_text = (f"{from_user['first_name']} {from_user['last_name']} "
f"posted on {owner_user['first_name']} "
f"{owner_user['last_name']}'s wall:")
processed_posts.append({
"author": render_text(author_text),
"text": render_text(post_text),
"has_attachments": len(post.get("attachments", [])) > 0,
"likes": post.get("likes", {}).get("count", 0), # Добавляем количество лайков
"separator": render_separator() if idx < len(feed_data["items"]) - 1 else None
})
return templates.TemplateResponse("feed_global.html", {
"request": request,
"posts": processed_posts,
"current_page": page,
"has_next": "next_from" in feed_data,
"global_feed": global_feed
})
@app.get("/post", response_class=HTMLResponse)
async def post_page(request: Request):
device_uuid = request.cookies.get("webtv_uuid")
storage = load_storage()
if not device_uuid or device_uuid not in storage:
return RedirectResponse(url="/", status_code=303)
return templates.TemplateResponse("post.html", {
"request": request,
"error": request.query_params.get("error"),
"success": "success" in request.query_params
})
@app.post("/post")
async def create_post(request: Request):
device_uuid = request.cookies.get("webtv_uuid")
storage = load_storage()
if not device_uuid or device_uuid not in storage:
return RedirectResponse(url="/", status_code=303)
user_data = storage[device_uuid]
form_data = await request.form()
post_text = form_data.get("text", "").strip()
if not post_text:
return RedirectResponse(url="/post?error=empty", status_code=303)
try:
# Get owner_id from profile info
async with httpx.AsyncClient() as client:
profile_response = await client.get(
f"{user_data['instance']}/method/Account.getProfileInfo",
params={"access_token": user_data["token"]}
)
profile_response.raise_for_status()
owner_id = profile_response.json()["response"]["id"]
# Send post
post_response = await client.get(
f"{user_data['instance']}/method/wall.post",
params={
"access_token": user_data["token"],
"owner_id": owner_id,
"message": post_text
}
)
post_response.raise_for_status()
except Exception as e:
print(f"Post error: {str(e)}")
return RedirectResponse(url="/post?error=api", status_code=303)
return RedirectResponse(url="/post?success=1", status_code=303)
@app.get("/about", response_class=HTMLResponse)
async def about_page(request: Request):
return templates.TemplateResponse("about.html", {"request": request})

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
fastapi==0.116.1
httpx==0.28.1
Pillow==11.3.0

BIN
static/deepseek.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/me.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/text/no_name.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

20
templates/about.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>OpenVK4WebTV - About</TITLE>
<STYLE>
Body { font-family: Arial; background: #EEE; }
.container { width: 300px; margin: 50px auto; padding: 20px; background: white; }
.field { margin: 10px 0; }
INPUT { width: 100%; padding: 2px; }
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<P><B>OpenVK4WebTV</B> is a simple ass OpenVK client for WebTVs (and maybe super old web browsers)</P>
<BR>
<P>Made by me... and DeepSeek (cuz i suck at everything)</P>
<IMG SRC="/static/me.gif"><IMG SRC="/static/deepseek.gif">
</DIV>
</BODY>
</HTML>

85
templates/feed.html Normal file
View File

@@ -0,0 +1,85 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>News Feed - OpenVK4WebTV</TITLE>
<STYLE>
Body {
font-family: Arial;
background: #EEE;
margin: 0;
padding: 10px;
}
.container {
background: white;
padding: 15px;
max-width: 500px;
margin: 0 auto;
}
.post {
margin-bottom: 15px;
}
.post IMG {
display: block;
border: 1px solid #DDD;
}
.separator {
height: 2px;
background: #DDD;
margin: 10px 0;
}
.pagination {
text-align: center;
margin: 15px 0;
}
.pagination A {
margin: 0 5px;
color: #00F;
text-decoration: none;
}
.likes {
margin-top: 5px;
color: #666;
}
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<H3>{% if global_feed %}Global {% endif %}News Feed</H3>
{% for post in posts %}
<DIV CLASS="post">
<IMG SRC="{{ post.author }}" ALT="Author"><BR>
<IMG SRC="{{ post.text }}" ALT="Post text">
{% if post.has_attachments %}
<DIV CLASS="attachments">
[Attachments not supported]
</DIV>
{% endif %}
<!-- Блок с лайками -->
{% if post.likes > 0 %}
<DIV CLASS="likes">
<IMG SRC="{{ render_text('Likes: ' + post.likes|string) }}" ALT="Likes">
</DIV>
{% endif %}
{% if post.separator %}
<BR>
<IMG SRC="{{ post.separator }}" ALT="Separator">
{% endif %}
</DIV>
{% endfor %}
<DIV CLASS="pagination">
{% if current_page > 0 %}
<A HREF="/feed?page={{ current_page - 1 }}&global={{ 1 if global_feed else 0 }}">Previous</A>
{% endif %}
{% if has_next %}
<A HREF="/feed?page={{ current_page + 1 }}&global={{ 1 if global_feed else 0 }}">Next</A>
{% endif %}
</DIV>
<P><A HREF="/profile">Profile</A> | <A HREF="/feed_global">Global Feed</A> | <A HREF="/post">New Post</A></P>
</BODY>
</HTML>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>Global News Feed - OpenVK4WebTV</TITLE>
<STYLE>
Body {
font-family: Arial;
background: #EEE;
margin: 0;
padding: 10px;
}
.container {
background: white;
padding: 15px;
max-width: 500px;
margin: 0 auto;
}
.post {
margin-bottom: 15px;
}
.post IMG {
display: block;
border: 1px solid #DDD;
}
.separator {
height: 2px;
background: #DDD;
margin: 10px 0;
}
.pagination {
text-align: center;
margin: 15px 0;
}
.pagination A {
margin: 0 5px;
color: #00F;
text-decoration: none;
}
.likes {
margin-top: 5px;
color: #666;
}
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<H3>{% if global_feed %}Global {% endif %}News Feed</H3>
{% for post in posts %}
<DIV CLASS="post">
<IMG SRC="{{ post.author }}" ALT="Author"><BR>
<IMG SRC="{{ post.text }}" ALT="Post text">
{% if post.has_attachments %}
<DIV CLASS="attachments">
[Attachments not supported]
</DIV>
{% endif %}
<!-- Блок с лайками -->
{% if post.likes > 0 %}
<DIV CLASS="likes">
<IMG SRC="{{ render_text('Likes: ' + post.likes|string) }}" ALT="Likes">
</DIV>
{% endif %}
{% if post.separator %}
<BR>
<IMG SRC="{{ post.separator }}" ALT="Separator">
{% endif %}
</DIV>
{% endfor %}
<DIV CLASS="pagination">
{% if current_page > 0 %}
<A HREF="/feed?page={{ current_page - 1 }}">Previous</A>
{% endif %}
{% if has_next %}
<A HREF="/feed?page={{ current_page + 1 }}">Next</A>
{% endif %}
</DIV>
<P><A HREF="/profile">Profile</A> | <A HREF="/feed">Personal Feed</A> | <A HREF="/post">New Post</A></P>
</BODY>
</HTML>

49
templates/index.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>OpenVK4WebTV</TITLE>
<STYLE>
Body { font-family: Arial; background: #EEE; }
.container { width: 300px; margin: 50px auto; padding: 20px; background: white; }
.field { margin: 10px 0; }
INPUT { width: 100%; padding: 2px; }
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<H3>OpenVK4WebTV</H3>
{% if request.query_params.get('error') %}
<DIV STYLE="color: red; margin: 10px 0;">
{% if request.query_params.get('error') == '1' %}
All fields are required!
{% elif request.query_params.get('error') == '2' %}
Authentication failed!
{% elif request.query_params.get('error') == '3' %}
Profile loading error!
{% elif request.query_params.get('error') == '4' %}
Feed loading error!
{% elif request.query_params.get('error') == '5' %}
Users info loading error!
{% endif %}
</DIV>
{% endif %}
<FORM METHOD="POST" ACTION="/auth">
<DIV CLASS="field">
Instance URL:<BR>
<INPUT TYPE="text" NAME="instance">
</DIV>
<DIV CLASS="field">
Email:<BR>
<INPUT TYPE="text" NAME="username">
</DIV>
<DIV CLASS="field">
Password:<BR>
<INPUT TYPE="password" NAME="password">
</DIV>
<INPUT TYPE="submit" VALUE="Login" STYLE="width: auto; padding: 3px 10px;">
<P><A HREF="/profile">Profile (if already logged in)</A></P>
<P><A HREF="/about">About</A></P>
</FORM>
</DIV>
</BODY>
</HTML>

40
templates/post.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>New Post - OpenVK4WebTV</TITLE>
<STYLE>
Body { font-family: Arial; background: #EEE; }
.container { width: 500px; margin: 20px auto; padding: 15px; background: white; }
TEXTAREA { width: 100%; height: 100px; margin: 10px 0; }
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<H3>Create New Post</H3>
{% if error %}
<DIV STYLE="color: red; margin: 10px 0;">
{% if error == 'empty' %}Post text is required!
{% elif error == 'api' %}Posting failed!
{% endif %}
</DIV>
{% endif %}
{% if success %}
<DIV STYLE="color: green; margin: 10px 0;">
Post published successfully!
</DIV>
{% endif %}
<FORM METHOD="POST" ACTION="/post">
<TEXTAREA NAME="text" WRAP="physical"></TEXTAREA><BR>
<INPUT TYPE="submit" VALUE="Publish">
</FORM>
<P STYLE="margin-top: 15px;">
<A HREF="/profile">Profile</A> |
<A HREF="/feed">News Feed</A>
</P>
</DIV>
</BODY>
</HTML>

39
templates/profile.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>Profile - OpenVK4WebTV</TITLE>
<STYLE>
Body { font-family: Arial; background: #EEE; }
.container { width: 500px; margin: 20px auto; padding: 15px; background: white; }
.avatar { border: 1px solid #999; margin: 10px 0; }
.info { margin: 10px 0; }
INPUT[type="submit"] {
background: #CC0000;
color: white;
border: 1px solid #990000;
padding: 5px 15px;
font-weight: bold;
}
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="container">
<H3>User Profile</H3>
<DIV CLASS="avatar">
<IMG SRC="{{ avatar }}" WIDTH="128" HEIGHT="128" ALT="Avatar">
</DIV>
<DIV CLASS="info">
<B>Name:</B><BR>
<IMG SRC="{{ first_name_img }}" ALT="First Name">
<IMG SRC="{{ last_name_img }}" ALT="Last Name"><BR>
<B>User ID:</B> {{ user_id }}
</DIV>
<P><A HREF="/feed">News Feed</A></P>
<P><A HREF="/post">New Post</A></P>
<P><A HREF="/">Login into other account</A></P>
</DIV>
</BODY>
</HTML>

18
templates/token.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Token</title>
<style>
body { font-family: Arial; padding: 20px; }
.token-box { background: #f5f5f5; padding: 15px; margin: 10px 0; }
</style>
</head>
<body>
<h3>Auth Success!</h3>
<div class="token-box">
<strong>Device UUID:</strong> {{ uuid }}<br>
<strong>Access Token:</strong> {{ token }}
</div>
<p>This UUID has been saved in cookies</p>
</body>
</html>