implement custom api url for sef hosted instances

This commit is contained in:
rambros 2026-02-25 22:22:40 +05:30
parent ed0e79d135
commit 34f9c9afa4
4 changed files with 197 additions and 114 deletions

View file

@ -35,12 +35,14 @@ class MigrationContext:
self.fluxer_writer = FluxerWriter( self.fluxer_writer = FluxerWriter(
token=config.fluxer_bot_token, token=config.fluxer_bot_token,
community_id=config.fluxer_community_id community_id=config.fluxer_community_id,
api_url=config.fluxer_api_url
) )
self.stoat_writer = StoatWriter( self.stoat_writer = StoatWriter(
token=config.stoat_bot_token, token=config.stoat_bot_token,
community_id=config.stoat_server_id community_id=config.stoat_server_id,
api_url=config.stoat_api_url
) )
self.writer = self.fluxer_writer if target_platform == "fluxer" else self.stoat_writer self.writer = self.fluxer_writer if target_platform == "fluxer" else self.stoat_writer

View file

@ -6,9 +6,10 @@ from fluxer import Bot, Webhook, Forbidden
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FluxerWriter: class FluxerWriter:
def __init__(self, token: str, community_id: str): def __init__(self, token: str, community_id: str, api_url: str = "default"):
self.token = token self.token = token
self.community_id = str(community_id) self.community_id = str(community_id)
self.api_url = api_url
self.bot: Optional[Bot] = None self.bot: Optional[Bot] = None
self._bot_task: Optional[asyncio.Task] = None self._bot_task: Optional[asyncio.Task] = None
self._ready_event = asyncio.Event() self._ready_event = asyncio.Event()
@ -45,7 +46,11 @@ class FluxerWriter:
if self.bot and self._bot_task and not self._bot_task.done(): if self.bot and self._bot_task and not self._bot_task.done():
return return
self.bot = Bot() bot_kwargs = {}
if self.api_url and self.api_url != "default":
bot_kwargs["api_url"] = self.api_url
self.bot = Bot(**bot_kwargs)
self._ready_event.clear() self._ready_event.clear()
# Define a simple on_ready listener to signal when we're connected # Define a simple on_ready listener to signal when we're connected

View file

@ -5,12 +5,17 @@ from typing import Optional, List, Dict, Any
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class StoatWriter: class StoatWriter:
def __init__(self, token: str, community_id: str): def __init__(self, token: str, community_id: str, api_url: str = "default"):
self.token = token self.token = token
self.community_id = str(community_id) self.community_id = str(community_id)
self.api_url = api_url
async def start(self): async def start(self):
self.client = stoat.Client(token=self.token, bot=True) client_kwargs = {"token": self.token, "bot": True}
if self.api_url and self.api_url != "default":
client_kwargs["http_base"] = self.api_url
self.client = stoat.Client(**client_kwargs)
self._server = None self._server = None
self._me = None self._me = None
try: try:
@ -45,7 +50,11 @@ class StoatWriter:
} }
# Use a temporary client for validation # Use a temporary client for validation
client = stoat.Client(token=self.token, bot=True) client_kwargs = {"token": self.token, "bot": True}
if self.api_url and self.api_url != "default":
client_kwargs["http_base"] = self.api_url
client = stoat.Client(**client_kwargs)
try: try:
# Validate token by fetching current user # Validate token by fetching current user
try: try:

View file

@ -3,6 +3,7 @@ import asyncio
import logging import logging
import re import re
import time import time
import aiohttp
from rich.console import Console from rich.console import Console
from rich.prompt import Prompt, Confirm from rich.prompt import Prompt, Confirm
from rich.table import Table from rich.table import Table
@ -337,14 +338,21 @@ class MigrationCLI:
if not skip_validation: if not skip_validation:
await self.validate_config() await self.validate_config()
# Display Required Permissions FIRST while True:
def fmt(name, val): console.clear()
if val is None: return f"[dim]{name}[/dim]" # Unknown console.print(Panel(f"[bold blue]Configuration: {self.target_platform.capitalize()}[/bold blue]", expand=False))
return f"[green]{name}[/green]" if val else f"[red]{name} [MISSING][/red]"
d_intents = self.validation_results.get("discord_intents", {}) # Print permissions summary
d_perms = self.validation_results.get("discord_permissions", {}) v = self.validation_results
f_perms = self.validation_results.get("fluxer_permissions", {}) s_perms = v.get(f"{self.target_platform}_permissions", {})
d_perms = v.get("discord_permissions", {})
d_intents = v.get("discord_intents", {})
def fmt(name, has):
if has is None: return f"[dim]{name}[/dim]"
color = "green" if has else "red"
symbol = "" if has else ""
return f"[{color}]{symbol} {name}[/{color}]"
perm_table = Table(show_header=True, header_style="bold magenta", box=None, padding=(0, 2)) perm_table = Table(show_header=True, header_style="bold magenta", box=None, padding=(0, 2))
perm_table.add_column("Bot Type") perm_table.add_column("Bot Type")
@ -355,14 +363,14 @@ class MigrationCLI:
f"• [bold]Intents:[/bold] Server Members, {fmt('Message Content', d_intents.get('message_content'))}\n" f"• [bold]Intents:[/bold] Server Members, {fmt('Message Content', d_intents.get('message_content'))}\n"
f"• [bold]Permissions:[/bold] {fmt('View Channels', d_perms.get('view_channel'))}, {fmt('Read Message History', d_perms.get('read_message_history'))}" f"• [bold]Permissions:[/bold] {fmt('View Channels', d_perms.get('view_channel'))}, {fmt('Read Message History', d_perms.get('read_message_history'))}"
) )
if self.target_platform == "fluxer": if self.target_platform == "fluxer":
perm_table.add_row( perm_table.add_row(
"[bold #4641D9]Fluxer Bot[/bold #4641D9]", "[bold #4641D9]Fluxer Bot[/bold #4641D9]",
f"• [bold]Permissions:[/bold] {fmt('Manage Channels', f_perms.get('manage_channels'))}, {fmt('Manage Messages', f_perms.get('manage_messages'))},\n" f"• [bold]Permissions:[/bold] {fmt('Manage Channels', s_perms.get('manage_channels'))}, {fmt('Manage Messages', s_perms.get('manage_messages'))},\n"
f" {fmt('Manage Roles', f_perms.get('manage_roles'))}, {fmt('Manage Emojis/Stickers', f_perms.get('manage_emojis_stickers'))}, {fmt('Manage Webhooks', f_perms.get('manage_webhooks'))}" f" {fmt('Manage Roles', s_perms.get('manage_roles'))}, {fmt('Manage Emojis/Stickers', s_perms.get('manage_emojis_stickers'))}, {fmt('Manage Webhooks', s_perms.get('manage_webhooks'))}"
) )
else: else:
s_perms = self.validation_results.get("stoat_permissions", {})
perm_table.add_row( perm_table.add_row(
"[bold #FF8C00]Stoat Bot[/bold #FF8C00]", "[bold #FF8C00]Stoat Bot[/bold #FF8C00]",
f"• [bold]Permissions:[/bold] {fmt('Manage Channel', s_perms.get('manage_channels'))}, {fmt('Manage Server', s_perms.get('manage_server'))},\n" f"• [bold]Permissions:[/bold] {fmt('Manage Channel', s_perms.get('manage_channels'))}, {fmt('Manage Server', s_perms.get('manage_server'))},\n"
@ -382,15 +390,17 @@ class MigrationCLI:
return f"{status} \"{name}\"" return f"{status} \"{name}\""
return status return status
console.print(f"Discord Bot Token {get_status_str(self.validation_results.get('discord_token', False), self.validation_results.get('discord_bot_name'))}") console.print(f"Discord Bot Token {get_status_str(v.get('discord_token', False), v.get('discord_bot_name'))}")
console.print(f"Discord Server ID {get_status_str(self.validation_results.get('discord_server', False), self.validation_results.get('discord_server_name'))}") console.print(f"Discord Server ID {get_status_str(v.get('discord_server', False), v.get('discord_server_name'))}")
if self.target_platform == "fluxer": if self.target_platform == "fluxer":
console.print(f"Fluxer Bot Token {get_status_str(self.validation_results.get('fluxer_token', False), self.validation_results.get('fluxer_bot_name'))}") console.print(f"Fluxer Bot Token {get_status_str(v.get('fluxer_token', False), v.get('fluxer_bot_name'))}")
console.print(f"Fluxer Community ID {get_status_str(self.validation_results.get('fluxer_community', False), self.validation_results.get('fluxer_community_name'))}") console.print(f"Fluxer Community ID {get_status_str(v.get('fluxer_community', False), v.get('fluxer_community_name'))}")
console.print(f"Fluxer API URL [dim]({self.config.fluxer_api_url})[/dim]")
else: else:
console.print(f"Stoat Bot Token {get_status_str(self.validation_results.get('stoat_token', False), self.validation_results.get('stoat_bot_name'))}") console.print(f"Stoat Bot Token {get_status_str(v.get('stoat_token', False), v.get('stoat_bot_name'))}")
console.print(f"Stoat Server ID {get_status_str(self.validation_results.get('stoat_server', False), self.validation_results.get('stoat_server_name'))}") console.print(f"Stoat Server ID {get_status_str(v.get('stoat_server', False), v.get('stoat_server_name'))}")
console.print(f"Stoat API URL [dim]({self.config.stoat_api_url})[/dim]")
console.print("\n(1) Edit Bot tokens & Community IDs") console.print("\n(1) Edit Bot tokens & Community IDs")
console.print("(2) Edit API url (for self hosted instances)") console.print("(2) Edit API url (for self hosted instances)")
@ -402,8 +412,63 @@ class MigrationCLI:
return return
if menu_choice == "2": if menu_choice == "2":
console.print("[yellow]Not implemented yet.[/yellow]") console.print("\n[bold]API URL Editor[/bold] (leave blank to keep current)")
return
if self.target_platform == "fluxer":
f_url = Prompt.ask("Fluxer API URL", default=self.config.fluxer_api_url)
s_url = self.config.stoat_api_url
changed = f_url != self.config.fluxer_api_url
else:
s_url = Prompt.ask("Stoat API URL", default=self.config.stoat_api_url)
f_url = self.config.fluxer_api_url
changed = s_url != self.config.stoat_api_url
if not changed:
console.print("[yellow]No changes made.[/yellow]")
time.sleep(1)
continue
# Validation logic
all_valid = True
with console.status("[bold yellow]Validating API endpoint..."):
async def check_api(url, name):
if url == "default":
return True
try:
# Use a 5 second timeout for validation
async with aiohttp.ClientSession() as session:
async with session.get(url.rstrip("/") + "/", timeout=5.0) as resp:
if resp.status >= 500:
console.print(f"[red]Error: {name} API returned server error {resp.status}[/red]")
return False
return True
except Exception as e:
console.print(f"[red]Error connecting to {name} API ({url}): {e}[/red]")
return False
if self.target_platform == "fluxer":
if not await check_api(f_url, "Fluxer"): all_valid = False
else:
if not await check_api(s_url, "Stoat"): all_valid = False
if all_valid:
console.print("[bold green]API endpoint valid[/bold green]")
else:
console.print("[bold red]API validation failed![/bold red]")
if Confirm.ask("Save config?", default=all_valid):
self.config.fluxer_api_url = f_url
self.config.stoat_api_url = s_url
save_config(self.config, self.config_path)
# Recreate engine with new config
self.engine = MigrationContext(self.config, self.target_platform)
console.print(f"\n[bold green]API URL updated and saved to {self.config_path}![/bold green]")
await self.update_validation_status()
else:
console.print("[yellow]Changes discarded.[/yellow]")
time.sleep(1)
continue
console.print("\n[bold]Configuration Editor[/bold] (leave blank to keep current)") console.print("\n[bold]Configuration Editor[/bold] (leave blank to keep current)")
@ -455,6 +520,8 @@ class MigrationCLI:
else: else:
console.print("[yellow]No changes made.[/yellow]") console.print("[yellow]No changes made.[/yellow]")
time.sleep(1) # Small delay to see status before refresh
async def update_validation_status(self): async def update_validation_status(self):
await self.validate_config() await self.validate_config()