feat: Add terminal image endpoint and save images
Introduces a new API endpoint `/terminal_images` to serve generated terminal output images. - Creates a new file `api_service/terminal_images_endpoint.py` to handle the static file serving. - Modifies `api_service/api_server.py` to mount the new endpoint. - Updates `cogs/terminal_cog.py` to save generated terminal images to a local directory (`terminal_images`) with unique filenames. - Adds a base URL constant (`API_BASE_URL`) to `cogs/terminal_cog.py` for potential future use in generating image URLs.
This commit is contained in:
parent
e7719406bf
commit
c9d6cc40a6
@ -567,6 +567,16 @@ app.mount("/dashboard/api", dashboard_api_app) # Mount the new dashboard API
|
||||
try:
|
||||
from api_service.webhook_endpoints import router as webhook_router # Relative import
|
||||
app.mount("/webhook", webhook_router) # Mount directly on the main app for simplicity
|
||||
|
||||
# Import and mount terminal images endpoint
|
||||
try:
|
||||
from api_service.terminal_images_endpoint import mount_terminal_images
|
||||
# Mount terminal images directory as static files
|
||||
mount_terminal_images(app)
|
||||
log.info("Terminal images endpoint mounted successfully")
|
||||
except ImportError as e:
|
||||
log.error(f"Could not import terminal images endpoint: {e}")
|
||||
log.error("Terminal images endpoint will not be available")
|
||||
# After mounting the webhook router
|
||||
log.info("Available routes in webhook_router:")
|
||||
from fastapi.routing import APIRoute, Mount
|
||||
|
57
api_service/terminal_images_endpoint.py
Normal file
57
api_service/terminal_images_endpoint.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""
|
||||
Terminal Images Endpoint
|
||||
|
||||
This module provides an endpoint for serving terminal images generated by the terminal_cog.py.
|
||||
"""
|
||||
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from typing import Optional
|
||||
|
||||
# Create a router for the terminal images endpoint
|
||||
router = APIRouter(tags=["Terminal Images"])
|
||||
|
||||
# Path to the terminal_images directory
|
||||
TERMINAL_IMAGES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'terminal_images'))
|
||||
|
||||
# Ensure the terminal_images directory exists
|
||||
os.makedirs(TERMINAL_IMAGES_DIR, exist_ok=True)
|
||||
|
||||
@router.get("/{filename}")
|
||||
async def get_terminal_image(filename: str):
|
||||
"""
|
||||
Get a terminal image by filename.
|
||||
|
||||
Args:
|
||||
filename: The filename of the terminal image
|
||||
|
||||
Returns:
|
||||
The terminal image file
|
||||
|
||||
Raises:
|
||||
HTTPException: If the file is not found
|
||||
"""
|
||||
file_path = os.path.join(TERMINAL_IMAGES_DIR, filename)
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
raise HTTPException(status_code=404, detail="Terminal image not found")
|
||||
|
||||
return FileResponse(file_path)
|
||||
|
||||
# Function to mount the terminal images directory as static files
|
||||
def mount_terminal_images(app):
|
||||
"""
|
||||
Mount the terminal_images directory as static files.
|
||||
|
||||
Args:
|
||||
app: The FastAPI app to mount the static files on
|
||||
"""
|
||||
# Check if the directory exists
|
||||
if os.path.exists(TERMINAL_IMAGES_DIR) and os.path.isdir(TERMINAL_IMAGES_DIR):
|
||||
# Mount the terminal_images directory as static files
|
||||
app.mount("/terminal_images", StaticFiles(directory=TERMINAL_IMAGES_DIR), name="terminal_images")
|
||||
print(f"Mounted terminal images directory: {TERMINAL_IMAGES_DIR}")
|
||||
else:
|
||||
print(f"Warning: Terminal images directory '{TERMINAL_IMAGES_DIR}' not found. Terminal images will not be available.")
|
@ -5,6 +5,8 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
import subprocess
|
||||
import os
|
||||
import io
|
||||
import uuid
|
||||
import time
|
||||
from collections import deque
|
||||
import shlex # For safer command parsing if not using shell=True for everything
|
||||
|
||||
@ -24,6 +26,9 @@ MAX_HISTORY_LINES = 500 # Max lines to keep in history
|
||||
AUTO_UPDATE_INTERVAL_SECONDS = 3
|
||||
MAX_OUTPUT_LINES_PER_IMAGE = (IMG_HEIGHT - 2 * PADDING) // (FONT_SIZE + LINE_SPACING)
|
||||
OWNER_ID = 452666956353503252
|
||||
TERMINAL_IMAGES_DIR = "terminal_images" # Directory to store terminal images
|
||||
# Use your actual domain or IP address here
|
||||
API_BASE_URL = "https://slipstreamm.dev" # Base URL for the API
|
||||
|
||||
# --- Helper: Owner Check ---
|
||||
async def is_owner_check(interaction: discord.Interaction) -> bool:
|
||||
@ -69,8 +74,11 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
# Fallback or raise error
|
||||
# self.owner_id = YOUR_FALLBACK_OWNER_ID # if you have one
|
||||
|
||||
def _generate_terminal_image(self) -> io.BytesIO:
|
||||
"""Generates an image of the current terminal output."""
|
||||
def _generate_terminal_image(self) -> tuple[io.BytesIO, str]:
|
||||
"""
|
||||
Generates an image of the current terminal output.
|
||||
Returns a tuple of (BytesIO object, filename)
|
||||
"""
|
||||
image = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), BACKGROUND_COLOR)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
@ -108,10 +116,22 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
if y_pos > IMG_HEIGHT - PADDING - FONT_SIZE:
|
||||
break # Stop if no more space
|
||||
|
||||
# Create a unique filename with timestamp
|
||||
filename = f"terminal_{uuid.uuid4().hex[:8]}_{int(time.time())}.png"
|
||||
|
||||
# Ensure the terminal_images directory exists
|
||||
os.makedirs(TERMINAL_IMAGES_DIR, exist_ok=True)
|
||||
|
||||
# Save the image to the terminal_images directory
|
||||
file_path = os.path.join(TERMINAL_IMAGES_DIR, filename)
|
||||
image.save(file_path, format='PNG')
|
||||
|
||||
# Also return a BytesIO object for backward compatibility
|
||||
img_byte_arr = io.BytesIO()
|
||||
image.save(img_byte_arr, format='PNG')
|
||||
img_byte_arr.seek(0)
|
||||
return img_byte_arr
|
||||
|
||||
return img_byte_arr, filename
|
||||
|
||||
async def _update_terminal_message(self, interaction: Interaction | None = None, new_content: str | None = None):
|
||||
"""Updates the terminal message with a new image and view."""
|
||||
@ -125,19 +145,18 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
await self.terminal_message.edit(content="Terminal session ended.", view=None, attachments=[])
|
||||
return
|
||||
|
||||
image_bytes = self._generate_terminal_image()
|
||||
discord_file = File(fp=image_bytes, filename="terminal_output.png")
|
||||
# Generate the image and save it to the terminal_images directory
|
||||
image_bytes, filename = self._generate_terminal_image()
|
||||
|
||||
# Create the URL for the image
|
||||
image_url = f"{API_BASE_URL}/terminal_images/{filename}"
|
||||
|
||||
if self.terminal_view:
|
||||
self.terminal_view.update_button_states(self) # Update button enable/disable
|
||||
|
||||
target_message = self.terminal_message
|
||||
edit_kwargs = {"attachments": [discord_file], "view": self.terminal_view}
|
||||
if new_content:
|
||||
edit_kwargs["content"] = new_content
|
||||
else: # Clear old content if any
|
||||
edit_kwargs["content"] = None
|
||||
|
||||
# Prepare the message content with the image URL
|
||||
content = f"Terminal Output: [View Image]({image_url})" if not new_content else new_content
|
||||
edit_kwargs = {"content": content, "view": self.terminal_view, "attachments": []}
|
||||
|
||||
try:
|
||||
if interaction and not interaction.response.is_done():
|
||||
@ -213,13 +232,16 @@ class TerminalCog(commands.Cog, name="Terminal"):
|
||||
|
||||
self.terminal_view = TerminalView(cog=self, owner_id=self.owner_id)
|
||||
|
||||
image_bytes = self._generate_terminal_image()
|
||||
discord_file = File(fp=image_bytes, filename="terminal_output.png")
|
||||
# Generate the image and save it to the terminal_images directory
|
||||
_, filename = self._generate_terminal_image()
|
||||
|
||||
# Create the URL for the image
|
||||
image_url = f"{API_BASE_URL}/terminal_images/{filename}"
|
||||
|
||||
# Send initial message and store it
|
||||
# Use followup since we deferred
|
||||
self.terminal_message = await interaction.followup.send(
|
||||
file=discord_file,
|
||||
content=f"Terminal Output: [View Image]({image_url})",
|
||||
view=self.terminal_view
|
||||
)
|
||||
self.terminal_view.message = self.terminal_message # Give view a reference to the message
|
||||
|
Loading…
x
Reference in New Issue
Block a user