update config to support multiple targets simultaneously
This commit is contained in:
parent
806966450b
commit
4b6e916425
4 changed files with 115 additions and 47 deletions
|
|
@ -8,9 +8,12 @@ class AppConfig(BaseModel):
|
||||||
discord_server_id: Optional[str] = Field(default=None)
|
discord_server_id: Optional[str] = Field(default=None)
|
||||||
tool_mode: str = Field(default="direct_transfer") # direct_transfer | backup_transfer | backup_only
|
tool_mode: str = Field(default="direct_transfer") # direct_transfer | backup_transfer | backup_only
|
||||||
target_platform: str = Field(default="fluxer") # fluxer | stoat | none
|
target_platform: str = Field(default="fluxer") # fluxer | stoat | none
|
||||||
target_bot_token: Optional[str] = Field(default=None)
|
fluxer_bot_token: Optional[str] = Field(default=None)
|
||||||
target_server_id: Optional[str] = Field(default=None)
|
fluxer_server_id: Optional[str] = Field(default=None)
|
||||||
target_api_url: Optional[str] = Field(default=None)
|
fluxer_api_url: Optional[str] = Field(default=None)
|
||||||
|
stoat_bot_token: Optional[str] = Field(default=None)
|
||||||
|
stoat_server_id: Optional[str] = Field(default=None)
|
||||||
|
stoat_api_url: Optional[str] = Field(default=None)
|
||||||
anonymize_users: bool = Field(default=False)
|
anonymize_users: bool = Field(default=False)
|
||||||
log_level: str = Field(default="DEBUG")
|
log_level: str = Field(default="DEBUG")
|
||||||
|
|
||||||
|
|
@ -27,28 +30,20 @@ class AppConfig(BaseModel):
|
||||||
return self.target_platform == "stoat"
|
return self.target_platform == "stoat"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fluxer_bot_token(self) -> Optional[str]:
|
def target_bot_token(self) -> Optional[str]:
|
||||||
return self.target_bot_token if self.target_platform == "fluxer" else None
|
return self.fluxer_bot_token if self.target_platform == "fluxer" else self.stoat_bot_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_server_id(self) -> Optional[str]:
|
||||||
|
return self.fluxer_server_id if self.target_platform == "fluxer" else self.stoat_server_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_api_url(self) -> Optional[str]:
|
||||||
|
return self.fluxer_api_url if self.target_platform == "fluxer" else self.stoat_api_url
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fluxer_community_id(self) -> Optional[str]:
|
def fluxer_community_id(self) -> Optional[str]:
|
||||||
return self.target_server_id if self.target_platform == "fluxer" else None
|
return self.fluxer_server_id
|
||||||
|
|
||||||
@property
|
|
||||||
def fluxer_api_url(self) -> Optional[str]:
|
|
||||||
return self.target_api_url if self.target_platform == "fluxer" else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stoat_bot_token(self) -> Optional[str]:
|
|
||||||
return self.target_bot_token if self.target_platform == "stoat" else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stoat_server_id(self) -> Optional[str]:
|
|
||||||
return self.target_server_id if self.target_platform == "stoat" else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stoat_api_url(self) -> Optional[str]:
|
|
||||||
return self.target_api_url if self.target_platform == "stoat" else None
|
|
||||||
|
|
||||||
def load_config(config_path: Union[str, Path] = "config.yaml", create_if_missing: bool = True) -> AppConfig:
|
def load_config(config_path: Union[str, Path] = "config.yaml", create_if_missing: bool = True) -> AppConfig:
|
||||||
path = Path(config_path)
|
path = Path(config_path)
|
||||||
|
|
@ -67,22 +62,22 @@ def load_config(config_path: Union[str, Path] = "config.yaml", create_if_missing
|
||||||
if not data:
|
if not data:
|
||||||
raise ValueError("Configuration file is empty or invalid YAML.")
|
raise ValueError("Configuration file is empty or invalid YAML.")
|
||||||
|
|
||||||
# ── migrate legacy configs that still have separate fluxer/stoat fields ──
|
# ── migrate legacy configs that used single target fields ──
|
||||||
if "fluxer_bot_token" in data or "stoat_bot_token" in data:
|
if "fluxer_community_id" in data:
|
||||||
if data.get("fluxer_bot_token") and data["fluxer_bot_token"] not in ("FLUXER_BOT_TOKEN", None):
|
data.setdefault("fluxer_server_id", data.pop("fluxer_community_id"))
|
||||||
data.setdefault("target_platform", "fluxer")
|
|
||||||
data.setdefault("target_bot_token", data["fluxer_bot_token"])
|
if "target_bot_token" in data or "target_server_id" in data:
|
||||||
data.setdefault("target_server_id", data.get("fluxer_community_id"))
|
platform = data.get("target_platform", "fluxer")
|
||||||
data.setdefault("target_api_url", data.get("fluxer_api_url") or "default")
|
if platform == "fluxer":
|
||||||
elif data.get("stoat_bot_token") and data["stoat_bot_token"] not in ("STOAT_BOT_TOKEN", None):
|
data.setdefault("fluxer_bot_token", data.get("target_bot_token"))
|
||||||
data.setdefault("target_platform", "stoat")
|
data.setdefault("fluxer_server_id", data.get("target_server_id"))
|
||||||
data.setdefault("target_bot_token", data["stoat_bot_token"])
|
data.setdefault("fluxer_api_url", data.get("target_api_url"))
|
||||||
data.setdefault("target_server_id", data.get("stoat_server_id"))
|
elif platform == "stoat":
|
||||||
data.setdefault("target_api_url", data.get("stoat_api_url") or "default")
|
data.setdefault("stoat_bot_token", data.get("target_bot_token"))
|
||||||
# Remove legacy keys so they don't conflict with the model
|
data.setdefault("stoat_server_id", data.get("target_server_id"))
|
||||||
for key in ("fluxer_bot_token", "fluxer_community_id", "fluxer_api_url",
|
data.setdefault("stoat_api_url", data.get("target_api_url"))
|
||||||
"stoat_bot_token", "stoat_server_id", "stoat_api_url",
|
|
||||||
"use_fluxer", "use_stoat"):
|
for key in ("target_bot_token", "target_server_id", "target_api_url", "use_fluxer", "use_stoat"):
|
||||||
data.pop(key, None)
|
data.pop(key, None)
|
||||||
|
|
||||||
return AppConfig(**data)
|
return AppConfig(**data)
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,9 @@ class MigrationDatabase:
|
||||||
Replaces the memory-bloated and O(N^2) JSON persistence for messages.
|
Replaces the memory-bloated and O(N^2) JSON persistence for messages.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_local = threading.local()
|
|
||||||
|
|
||||||
def __init__(self, db_path: Path):
|
def __init__(self, db_path: Path):
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
|
self._local = threading.local()
|
||||||
self._init_db()
|
self._init_db()
|
||||||
|
|
||||||
def _get_conn(self) -> sqlite3.Connection:
|
def _get_conn(self) -> sqlite3.Connection:
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ class ConfigSelectionScreen(Screen):
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
with Center():
|
with Center():
|
||||||
with Container(id="config_sel_container"):
|
with Container(id="config_sel_container"):
|
||||||
yield Label(f"{get_app_version()} — Select Configuration", id="config_sel_title")
|
yield Label(f"Reaper Configs", id="config_sel_title")
|
||||||
with VerticalScroll(id="config_list_container"):
|
with VerticalScroll(id="config_list_container"):
|
||||||
yield ListView(id="config_list")
|
yield ListView(id="config_list")
|
||||||
with Horizontal(id="config_sel_actions"):
|
with Horizontal(id="config_sel_actions"):
|
||||||
|
|
@ -449,15 +449,35 @@ class ConfigScreen(Screen):
|
||||||
coro = StoatWriter.fetch_guilds(token, api_url)
|
coro = StoatWriter.fetch_guilds(token, api_url)
|
||||||
else: return
|
else: return
|
||||||
|
|
||||||
|
# Use the platform-specific saved ID so we don't cross-contaminate
|
||||||
|
if platform == "fluxer":
|
||||||
|
saved_id = self.config.fluxer_server_id
|
||||||
|
elif platform == "stoat":
|
||||||
|
saved_id = self.config.stoat_server_id
|
||||||
|
else:
|
||||||
|
saved_id = None
|
||||||
|
|
||||||
await self._fetch_and_populate(
|
await self._fetch_and_populate(
|
||||||
coro,
|
coro,
|
||||||
"#btn_fetch_target_servers",
|
"#btn_fetch_target_servers",
|
||||||
"#inp_target_server",
|
"#inp_target_server",
|
||||||
f"No {platform} servers found.",
|
f"No {platform} servers found.",
|
||||||
self.config.target_server_id,
|
saved_id,
|
||||||
initial
|
initial
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Guard: if the user switched platforms while the fetch was in-flight,
|
||||||
|
# discard the stale results so they don't contaminate the wrong platform.
|
||||||
|
try:
|
||||||
|
if self._get_selected_platform() != platform:
|
||||||
|
select = self.query_one("#inp_target_server", Select)
|
||||||
|
select.set_options([])
|
||||||
|
select.value = Select.BLANK
|
||||||
|
select.prompt = "Validate Bot Token"
|
||||||
|
self.query_one("#btn_fetch_target_servers", Button).variant = "primary"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
if event.button.id == "btn_fetch_guilds":
|
if event.button.id == "btn_fetch_guilds":
|
||||||
|
|
@ -498,6 +518,30 @@ class ConfigScreen(Screen):
|
||||||
def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
|
def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
|
||||||
if event.radio_set.id == "mode_radio":
|
if event.radio_set.id == "mode_radio":
|
||||||
self._toggle_target_section()
|
self._toggle_target_section()
|
||||||
|
elif event.radio_set.id == "plat_radio":
|
||||||
|
plat = self._get_selected_platform()
|
||||||
|
try:
|
||||||
|
inp_token = self.query_one("#inp_target_token", Input)
|
||||||
|
inp_api = self.query_one("#inp_target_api", Input)
|
||||||
|
|
||||||
|
if plat == "fluxer":
|
||||||
|
inp_token.value = self.config.fluxer_bot_token or ""
|
||||||
|
api_val = self.config.fluxer_api_url
|
||||||
|
inp_api.value = api_val if (api_val and api_val != "default") else ""
|
||||||
|
elif plat == "stoat":
|
||||||
|
inp_token.value = self.config.stoat_bot_token or ""
|
||||||
|
api_val = self.config.stoat_api_url
|
||||||
|
inp_api.value = api_val if (api_val and api_val != "default") else ""
|
||||||
|
|
||||||
|
select = self.query_one("#inp_target_server", Select)
|
||||||
|
select.set_options([])
|
||||||
|
select.value = Select.BLANK
|
||||||
|
select.prompt = "Validate Bot Token"
|
||||||
|
self.query_one("#btn_fetch_target_servers", Button).variant = "primary"
|
||||||
|
|
||||||
|
if inp_token.value:
|
||||||
|
self.run_worker(self._do_fetch_target_servers(initial=True))
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
# ── save / start ─────────────────────────────────────────────────────
|
# ── save / start ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -514,15 +558,26 @@ class ConfigScreen(Screen):
|
||||||
|
|
||||||
# 3. Target Section
|
# 3. Target Section
|
||||||
if self.config.tool_mode != "backup_only":
|
if self.config.tool_mode != "backup_only":
|
||||||
self.config.target_platform = self._get_selected_platform()
|
plat = self._get_selected_platform()
|
||||||
self.config.target_bot_token = self.query_one("#inp_target_token", Input).value.strip() or None
|
self.config.target_platform = plat
|
||||||
|
token_val = self.query_one("#inp_target_token", Input).value.strip() or None
|
||||||
|
|
||||||
t_select = self.query_one("#inp_target_server", Select)
|
t_select = self.query_one("#inp_target_server", Select)
|
||||||
if t_select.value not in (Select.BLANK, Select.NULL):
|
|
||||||
self.config.target_server_id = str(t_select.value)
|
|
||||||
|
|
||||||
target_api = self.query_one("#inp_target_api", Input).value.strip()
|
target_api = self.query_one("#inp_target_api", Input).value.strip()
|
||||||
self.config.target_api_url = target_api or None
|
api_val = target_api or None
|
||||||
|
|
||||||
|
if plat == "fluxer":
|
||||||
|
self.config.fluxer_bot_token = token_val
|
||||||
|
# Only update server_id if user actually selected something
|
||||||
|
if t_select.value not in (Select.BLANK, Select.NULL):
|
||||||
|
self.config.fluxer_server_id = str(t_select.value)
|
||||||
|
self.config.fluxer_api_url = api_val
|
||||||
|
elif plat == "stoat":
|
||||||
|
self.config.stoat_bot_token = token_val
|
||||||
|
if t_select.value not in (Select.BLANK, Select.NULL):
|
||||||
|
self.config.stoat_server_id = str(t_select.value)
|
||||||
|
self.config.stoat_api_url = api_val
|
||||||
|
|
||||||
self.config.anonymize_users = self.query_one("#inp_anonymize_users", Switch).value
|
self.config.anonymize_users = self.query_one("#inp_anonymize_users", Switch).value
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,25 @@ class OperationPane(Container):
|
||||||
|
|
||||||
@work(exclusive=True)
|
@work(exclusive=True)
|
||||||
async def run_validate(self) -> None:
|
async def run_validate(self) -> None:
|
||||||
|
try:
|
||||||
|
plat = "Fluxer" if self.target_platform == "fluxer" else "Stoat"
|
||||||
|
self.query_one("#op_lbl_t_header", Label).update(plat)
|
||||||
|
self.query_one("#op_lbl_d_server", Label).update("Server: [yellow]Validating...[/yellow]")
|
||||||
|
self.query_one("#op_lbl_d_bot", Label).update("Source: [yellow]Validating...[/yellow]" if self.view_mode == "backup" else "Bot: [yellow]Validating...[/yellow]")
|
||||||
|
self.query_one("#op_lbl_d_status", Label).update("Status: [yellow]Validating...[/yellow]")
|
||||||
|
self.query_one("#op_lbl_t_comm", Label).update("Community: [yellow]Validating...[/yellow]")
|
||||||
|
self.query_one("#op_lbl_t_bot", Label).update("Bot: [yellow]Validating...[/yellow]")
|
||||||
|
self.query_one("#op_lbl_t_status", Label).update("Status: [yellow]Validating...[/yellow]")
|
||||||
|
# Disable all operation buttons while validation is in progress
|
||||||
|
if self.view_mode == "shuttle":
|
||||||
|
for bid in ("#op_clone", "#op_sync", "#op_messages", "#op_danger"):
|
||||||
|
self.query_one(bid, Button).disabled = True
|
||||||
|
elif self.view_mode == "backup":
|
||||||
|
for bid in ("#op_backup_msgs", "#op_backup_sync"):
|
||||||
|
self.query_one(bid, Button).disabled = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
self.validation_results = {
|
self.validation_results = {
|
||||||
"discord_token": False, "discord_bot_name": None,
|
"discord_token": False, "discord_bot_name": None,
|
||||||
"discord_server": False, "discord_server_name": None,
|
"discord_server": False, "discord_server_name": None,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue