quote / app.py
Reaperxxxx's picture
Update app.py
8ee8909 verified
from flask import Flask, request, send_file
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import textwrap
import io
import os
import math
from datetime import datetime
app = Flask(__name__)
class TelegramChatGenerator:
def __init__(self):
# Custom bubble colors
self.bubble_color = (31, 39, 69) # #1f2745
self.bubble_shadow = (20, 25, 45) # Darker shadow
self.reply_bubble_color = (25, 31, 55) # Darker for contrast
self.reply_line_color = (70, 130, 255) # Blue accent line
self.text_color = (255, 255, 255)
self.reply_text_color = (160, 170, 185)
self.username_color = (255, 255, 255)
self.time_color = (160, 170, 185)
# Load fonts with fallbacks
self.load_fonts()
# Standard square size
self.image_size = 800
def load_fonts(self):
"""Load fonts with multiple fallbacks"""
font_paths = [
"Roboto-Regular.ttf",
"Arial.ttf",
"Arial.ttf",
"arial.ttf",
"DejaVuSans.ttf"
]
try:
# Try to load a modern font
for path in font_paths:
if os.path.exists(path):
self.font = ImageFont.truetype(path, 22)
self.username_font = ImageFont.truetype(path, 18)
self.reply_font = ImageFont.truetype(path, 16)
self.time_font = ImageFont.truetype(path, 14)
return
# Fallback to default
self.font = ImageFont.load_default()
self.username_font = ImageFont.load_default()
self.reply_font = ImageFont.load_default()
self.time_font = ImageFont.load_default()
except:
self.font = ImageFont.load_default()
self.username_font = ImageFont.load_default()
self.reply_font = ImageFont.load_default()
self.time_font = ImageFont.load_default()
def wrap_text(self, text, font, max_width):
"""Smart text wrapping based on pixel width"""
words = text.split()
lines = []
current_line = []
for word in words:
test_line = ' '.join(current_line + [word])
bbox = font.getbbox(test_line)
width = bbox[2] - bbox[0]
if width <= max_width:
current_line.append(word)
else:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
else:
# Word is too long, force break
lines.append(word)
if current_line:
lines.append(' '.join(current_line))
return '\n'.join(lines)
def draw_telegram_bubble(self, draw, coords, radius, fill, shadow_offset=6):
"""Draw Telegram-style bubble with enhanced shadow"""
x1, y1, x2, y2 = coords
# Ensure coordinates are valid
if x2 <= x1 or y2 <= y1:
return
# Ensure radius doesn't exceed bubble dimensions
max_radius = min((x2 - x1) // 2, (y2 - y1) // 2, radius)
if max_radius < 1:
max_radius = 1
# Draw simple shadow first
if shadow_offset > 0:
shadow_coords = (x1 + shadow_offset, y1 + shadow_offset,
x2 + shadow_offset, y2 + shadow_offset)
shadow_color = (0, 0, 0, 60) # Semi-transparent black
self.draw_rounded_rectangle(draw, shadow_coords, max_radius, shadow_color)
# Draw main bubble
self.draw_rounded_rectangle(draw, coords, max_radius, fill)
# Add subtle inner highlight
if y2 - y1 > 6:
highlight_coords = (x1 + 1, y1 + 1, x2 - 1, y1 + 3)
lighter_fill = tuple(min(255, c + 15) for c in fill)
self.draw_rounded_rectangle(draw, highlight_coords, max_radius - 1, lighter_fill)
def draw_rounded_rectangle(self, draw, coords, radius, fill):
"""Draw a perfect rounded rectangle with bounds checking"""
x1, y1, x2, y2 = coords
# Ensure coordinates are valid
if x2 <= x1 or y2 <= y1:
return
# Limit radius to prevent invalid coordinates
width = x2 - x1
height = y2 - y1
max_radius = min(width // 2, height // 2, radius)
if max_radius < 1:
# If radius is too small, just draw a regular rectangle
draw.rectangle([x1, y1, x2, y2], fill=fill)
return
# Handle both RGB and RGBA fills
if len(fill) == 3:
# RGB - add full opacity
fill_color = fill + (255,)
else:
# RGBA - use as is
fill_color = fill
# Main rectangles (only if they have positive dimensions)
if width > 2 * max_radius:
draw.rectangle([x1 + max_radius, y1, x2 - max_radius, y2], fill=fill_color)
if height > 2 * max_radius:
draw.rectangle([x1, y1 + max_radius, x2, y2 - max_radius], fill=fill_color)
# Corner circles (only if there's space for them)
if width >= 2 * max_radius and height >= 2 * max_radius:
# Top-left
draw.ellipse([x1, y1, x1 + 2 * max_radius, y1 + 2 * max_radius], fill=fill_color)
# Top-right
draw.ellipse([x2 - 2 * max_radius, y1, x2, y1 + 2 * max_radius], fill=fill_color)
# Bottom-left
draw.ellipse([x1, y2 - 2 * max_radius, x1 + 2 * max_radius, y2], fill=fill_color)
# Bottom-right
draw.ellipse([x2 - 2 * max_radius, y2 - 2 * max_radius, x2, y2], fill=fill_color)
def get_text_dimensions(self, text, font):
"""Get accurate text dimensions"""
lines = text.split('\n')
max_width = 0
total_height = 0
for i, line in enumerate(lines):
bbox = font.getbbox(line)
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
max_width = max(max_width, width)
total_height += height
# Add line spacing except for last line
if i < len(lines) - 1:
total_height += 6
return max_width, total_height
def add_subtle_pattern(self, img):
"""Add a subtle dot pattern to the background"""
draw = ImageDraw.Draw(img)
width, height = img.size
# Create subtle dot pattern
for x in range(0, width, 40):
for y in range(0, height, 40):
# Vary opacity based on position
opacity = max(5, min(15, abs(x - width//2) // 20 + abs(y - height//2) // 20))
color = (255, 255, 255, opacity)
draw.ellipse([x, y, x + 2, y + 2], fill=color)
def generate_chat_bubble(self, message, username, reply_to=None, replied_username=None):
# Calculate bubble dimensions first to determine canvas size
padding = 40
bubble_padding = 25
# Estimate text dimensions
wrapped_message = self.wrap_text(message, self.font, 500) # Max width estimate
msg_width, msg_height = self.get_text_dimensions(wrapped_message, self.font)
username_width, username_height = self.get_text_dimensions(username, self.username_font)
# Handle reply dimensions
reply_height = 0
wrapped_reply = ""
if reply_to and replied_username:
truncated_reply = reply_to[:100] + "..." if len(reply_to) > 100 else reply_to
wrapped_reply = self.wrap_text(truncated_reply, self.reply_font, 450)
reply_username_w, reply_username_h = self.get_text_dimensions(replied_username, self.reply_font)
reply_msg_w, reply_msg_h = self.get_text_dimensions(wrapped_reply, self.reply_font)
reply_height = reply_username_h + reply_msg_h + 25
# Calculate bubble dimensions
bubble_width = max(300, min(600, msg_width + bubble_padding * 2 + 50))
bubble_height = max(100, username_height + msg_height + bubble_padding * 2 + 50)
if reply_to:
bubble_height += reply_height + 15
# Create canvas with transparent background, sized to fit bubble + padding
canvas_width = bubble_width + padding * 2
canvas_height = bubble_height + padding * 2
# Create image with transparent background
img = Image.new('RGBA', (canvas_width, canvas_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Position bubble in center of canvas
bubble_x = padding
bubble_y = padding
# Draw main bubble with enhanced shadow
self.draw_telegram_bubble(
draw,
[bubble_x, bubble_y, bubble_x + bubble_width, bubble_y + bubble_height],
radius=18,
fill=self.bubble_color,
shadow_offset=6
)
# Current position for content
content_y = bubble_y + bubble_padding
# Draw reply section if present
if reply_to and replied_username and bubble_width > 60:
reply_y = content_y
reply_bubble_height = max(30, reply_height + 10)
# Reply background
reply_bubble_x1 = bubble_x + 15
reply_bubble_x2 = bubble_x + bubble_width - 15
reply_bubble_y1 = reply_y
reply_bubble_y2 = reply_y + reply_bubble_height
if reply_bubble_x2 > reply_bubble_x1 and reply_bubble_y2 > reply_bubble_y1:
self.draw_rounded_rectangle(
draw,
[reply_bubble_x1, reply_bubble_y1, reply_bubble_x2, reply_bubble_y2],
radius=12,
fill=self.reply_bubble_color
)
# Blue accent line (thicker and more prominent)
if reply_bubble_height > 10:
draw.rectangle([bubble_x + 18, reply_y + 8, bubble_x + 22, reply_y + reply_bubble_height - 8],
fill=self.reply_line_color)
# Reply content
reply_content_x = bubble_x + 30
reply_content_y = reply_y + 12
# Reply username
if reply_content_x < bubble_x + bubble_width - 20:
draw.text((reply_content_x, reply_content_y), replied_username,
fill=self.reply_line_color, font=self.reply_font)
reply_content_y += 22
# Reply message
for line in wrapped_reply.split('\n'):
if reply_content_y < bubble_y + bubble_height - 30:
draw.text((reply_content_x, reply_content_y), line,
fill=self.reply_text_color, font=self.reply_font)
reply_content_y += 18
content_y += reply_bubble_height + 15
# Main message username
if content_y < bubble_y + bubble_height - 50:
draw.text((bubble_x + bubble_padding, content_y), username,
fill=self.username_color, font=self.username_font)
content_y += username_height + 12
# Main message text with better spacing
for line in wrapped_message.split('\n'):
if content_y < bubble_y + bubble_height - 40:
draw.text((bubble_x + bubble_padding, content_y), line,
fill=self.text_color, font=self.font)
content_y += 30
# Add timestamp in bottom right
current_time = datetime.now().strftime("%H:%M")
time_bbox = self.time_font.getbbox(current_time)
time_width = time_bbox[2] - time_bbox[0]
timestamp_x = bubble_x + bubble_width - time_width - bubble_padding - 30
timestamp_y = bubble_y + bubble_height - 28
if timestamp_x > bubble_x and timestamp_y > bubble_y:
draw.text((timestamp_x, timestamp_y),
current_time, fill=self.time_color, font=self.time_font)
# Add double checkmarks
check_x = bubble_x + bubble_width - 25
check_y = bubble_y + bubble_height - 25
if check_x > bubble_x and check_y > bubble_y:
self.draw_checkmarks(draw, check_x, check_y)
return img
def draw_checkmarks(self, draw, x, y):
"""Draw double checkmarks for message status"""
# First checkmark
points1 = [(x-8, y), (x-5, y+3), (x-2, y-2)]
self.draw_polyline(draw, points1, self.time_color, width=2)
# Second checkmark (overlapping)
points2 = [(x-5, y), (x-2, y+3), (x+2, y-2)]
self.draw_polyline(draw, points2, self.time_color, width=2)
def draw_polyline(self, draw, points, fill, width=1):
"""Draw a polyline (series of connected lines)"""
for i in range(len(points) - 1):
draw.line([points[i], points[i + 1]], fill=fill, width=width)
# Initialize generator
chat_gen = TelegramChatGenerator()
@app.route('/chat')
def generate_chat():
# Get parameters
message = request.args.get('message', 'Hello World! πŸ‘‹')
username = request.args.get('username', 'User')
reply_to = request.args.get('replyto')
replied_username = request.args.get('repliedusername')
# Generate image
img = chat_gen.generate_chat_bubble(
message=message,
username=username,
reply_to=reply_to,
replied_username=replied_username
)
# Return as PNG
img_io = io.BytesIO()
img.save(img_io, 'PNG', quality=95, optimize=True)
img_io.seek(0)
return send_file(img_io, mimetype='image/png', as_attachment=False)
@app.route('/')
def index():
return '''
<!DOCTYPE html>
<html>
<head>
<title>Telegram Chat Generator</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; margin-bottom: 30px; }
.example { margin: 15px 0; }
.example img { max-width: 300px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
.params { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; }
a { color: #007bff; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<h1>🎨 Premium Telegram Chat Generator</h1>
<div class="params">
<h3>✨ Features:</h3>
<ul>
<li>🎨 Beautiful gradient backgrounds</li>
<li>πŸ’¬ Authentic Telegram bubble design</li>
<li>↩️ Reply message support</li>
<li>πŸ“ Perfect square format (800x800)</li>
<li>πŸ•’ Automatic timestamps</li>
<li>βœ… Read status indicators</li>
<li>πŸ“± Smart text wrapping</li>
</ul>
</div>
<h3>πŸš€ Try These Examples:</h3>
<div class="example">
<h4>Simple Message:</h4>
<a href="/chat?message=Hey! How are you doing today? 😊&username=Alice">
<img src="/chat?message=Hey! How are you doing today? 😊&username=Alice" alt="Simple message example">
</a>
</div>
<div class="example">
<h4>Long Message:</h4>
<a href="/chat?message=I just wanted to let you know that the meeting has been rescheduled to tomorrow at 3 PM. Please make sure to bring all the documents we discussed earlier. Thanks!&username=John">
<img src="/chat?message=I just wanted to let you know that the meeting has been rescheduled to tomorrow at 3 PM. Please make sure to bring all the documents we discussed earlier. Thanks!&username=John" alt="Long message example">
</a>
</div>
<div class="example">
<h4>Reply Message:</h4>
<a href="/chat?message=Sounds great! I'll be there πŸ‘&username=Mike&replyto=Want to grab coffee tomorrow morning?&repliedusername=Sarah">
<img src="/chat?message=Sounds great! I'll be there πŸ‘&username=Mike&replyto=Want to grab coffee tomorrow morning?&repliedusername=Sarah" alt="Reply message example">
</a>
</div>
<div class="params">
<h3>πŸ”§ API Parameters:</h3>
<ul>
<li><code>message</code> - Your message text (supports emojis!)</li>
<li><code>username</code> - Sender's display name</li>
<li><code>replyto</code> - (Optional) Message being replied to</li>
<li><code>repliedusername</code> - (Optional) Original sender's name</li>
</ul>
<h4>Example URL:</h4>
<code>/chat?message=Hello World!&username=YourName&replyto=How are you?&repliedusername=Friend</code>
</div>
</div>
</body>
</html>
'''
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=7860)