improve role mapping and permission syncing

This commit is contained in:
rambros 2026-02-22 19:53:51 +05:30
parent c5abc6d0e5
commit ab31f889a4
4 changed files with 107 additions and 11 deletions

View file

@ -158,6 +158,36 @@ class MigrationEngine:
if updates > 0 or removals > 0: if updates > 0 or removals > 0:
logger.info(f"Channel sync: {updates} mapped, {removals} stale mappings removed") logger.info(f"Channel sync: {updates} mapped, {removals} stale mappings removed")
async def sync_roles_state(self):
"""
Scans Fluxer for roles matching Discord names and updates state.json mappings.
"""
discord_roles = await self.discord_reader.get_roles()
fluxer_roles = await self.fluxer_writer.client.get_guild_roles(self.config.fluxer_community_id)
# Build name -> id maps and ID sets for Fluxer for fast lookup
fluxer_role_map = {r.get("name"): str(r.get("id")) for r in fluxer_roles if r.get("name")}
fluxer_role_ids = {str(r.get("id")) for r in fluxer_roles}
updates = 0
removals = 0
# Verify and Sync Roles
for role in discord_roles:
discord_id = str(role.id)
fluxer_id = self.state.get_fluxer_role_id(discord_id)
if fluxer_id:
if fluxer_id not in fluxer_role_ids:
self.state.remove_role_mapping(discord_id)
removals += 1
elif role.name in fluxer_role_map:
self.state.set_role_mapping(discord_id, fluxer_role_map[role.name])
updates += 1
if updates > 0 or removals > 0:
logger.info(f"Role sync: {updates} mapped, {removals} stale mappings removed")
async def sync_assets_state(self): async def sync_assets_state(self):
""" """
Scans Fluxer for emojis and stickers matching Discord names and updates state.json mappings. Scans Fluxer for emojis and stickers matching Discord names and updates state.json mappings.
@ -310,12 +340,38 @@ class MigrationEngine:
if total == 0: if total == 0:
return return
async def _sync_overwrites(discord_item, fluxer_id):
"""Helper to sync role overwrites for a given channel or category."""
for target, overwrite in discord_item.overwrites.items():
if type(target).__name__ == "Role":
discord_role_id = str(target.id)
# Handle @everyone role special case
if discord_role_id == self.config.discord_server_id:
fluxer_role_id = self.config.fluxer_community_id
else:
fluxer_role_id = self.state.get_fluxer_role_id(discord_role_id)
if not fluxer_role_id:
continue
allow_val, deny_val = overwrite.pair()
await self.fluxer_writer.set_channel_permission(
channel_id=fluxer_id,
overwrite_id=fluxer_role_id,
allow=allow_val.value,
deny=deny_val.value,
is_role=True
)
# Sync Category Permissions (Role Overwrites) # Sync Category Permissions (Role Overwrites)
for cat in categories: for cat in categories:
if not self.is_running: break if not self.is_running: break
fluxer_id = self.state.get_fluxer_channel_id(str(cat.id)) fluxer_id = self.state.get_fluxer_category_id(str(cat.id))
# In a real implementation, we would diff discord perms if fluxer_id:
# and apply them to fluxer_id using client methods. try:
await _sync_overwrites(cat, fluxer_id)
except Exception as e:
logger.error(f"Failed syncing permissions for category {cat.name}: {e}")
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(f"Cat: {cat.name}", current_idx, total) if progress_callback: await progress_callback(f"Cat: {cat.name}", current_idx, total)
@ -324,7 +380,11 @@ class MigrationEngine:
for channel in channels: for channel in channels:
if not self.is_running: break if not self.is_running: break
fluxer_id = self.state.get_fluxer_channel_id(str(channel.id)) fluxer_id = self.state.get_fluxer_channel_id(str(channel.id))
# apply perms if fluxer_id:
try:
await _sync_overwrites(channel, fluxer_id)
except Exception as e:
logger.error(f"Failed syncing permissions for channel {channel.name}: {e}")
current_idx += 1 current_idx += 1
if progress_callback: await progress_callback(channel.name, current_idx, total) if progress_callback: await progress_callback(channel.name, current_idx, total)

View file

@ -107,6 +107,10 @@ class MigrationState:
def get_fluxer_role_id(self, discord_id: str) -> str | None: def get_fluxer_role_id(self, discord_id: str) -> str | None:
return self.role_map.get(str(discord_id)) return self.role_map.get(str(discord_id))
def remove_role_mapping(self, discord_id: str):
self.role_map.pop(str(discord_id), None)
self.save()
def set_emoji_mapping(self, discord_id: str, fluxer_id: str): def set_emoji_mapping(self, discord_id: str, fluxer_id: str):
self.emoji_map[str(discord_id)] = str(fluxer_id) self.emoji_map[str(discord_id)] = str(fluxer_id)
self.save() self.save()

View file

@ -436,9 +436,16 @@ class FluxerWriter:
overwrites = ch.get("permission_overwrites", []) overwrites = ch.get("permission_overwrites", [])
for ow in overwrites: for ow in overwrites:
try: try:
await self.client.delete_channel_permission(ch["id"], ow["id"]) await self.client.request(
except Exception: self.client._route(
pass "DELETE",
"/channels/{channel_id}/permissions/{overwrite_id}",
channel_id=ch["id"],
overwrite_id=ow["id"]
)
)
except Exception as e:
logger.error(f"Failed to delete overwrite {ow['id']} for channel {ch['id']}: {e}")
processed += 1 processed += 1
if progress_callback: if progress_callback:
await progress_callback(ch.get("name", "Unknown"), processed, total) await progress_callback(ch.get("name", "Unknown"), processed, total)
@ -446,6 +453,21 @@ class FluxerWriter:
print(f"Failed to reset permissions for channel {ch.get('name')}: {e}") print(f"Failed to reset permissions for channel {ch.get('name')}: {e}")
return processed return processed
async def set_channel_permission(self, channel_id: str, overwrite_id: str, allow: int, deny: int, is_role: bool = True):
"""Sets a permission overwrite for a channel or category."""
assert self.client is not None
try:
await self.client.edit_channel_permissions(
channel_id=int(channel_id),
overwrite_id=int(overwrite_id),
allow=allow,
deny=deny,
type=0 if is_role else 1
)
except Exception as e:
logger.error(f"Failed to set permission on channel {channel_id} for overwrite {overwrite_id}: {e}")
async def delete_all_roles(self, progress_callback=None) -> int: async def delete_all_roles(self, progress_callback=None) -> int:
""" """
Deletes all non-managed, non-default roles in the Fluxer community, Deletes all non-managed, non-default roles in the Fluxer community,

View file

@ -71,7 +71,8 @@ class MigrationCLI:
"discord_intents": {}, "discord_permissions": {}, "discord_intents": {}, "discord_permissions": {},
"fluxer_token": False, "fluxer_bot_name": None, "fluxer_token": False, "fluxer_bot_name": None,
"fluxer_community": False, "fluxer_community_name": None, "fluxer_community": False, "fluxer_community_name": None,
"fluxer_permissions": {} "fluxer_permissions": {},
"discord_timeout": False, "fluxer_timeout": False
} }
self.tokens_valid = False self.tokens_valid = False
@ -108,6 +109,7 @@ class MigrationCLI:
console.print(f"[bold red]Discord validation failed with error: {e}[/bold red]") console.print(f"[bold red]Discord validation failed with error: {e}[/bold red]")
else: else:
console.print("[bold red]Discord bot token validation timed out after 10 seconds.[/bold red]") console.print("[bold red]Discord bot token validation timed out after 10 seconds.[/bold red]")
self.validation_results["discord_timeout"] = True
discord_task.cancel() discord_task.cancel()
# Process Fluxer Result # Process Fluxer Result
@ -130,6 +132,7 @@ class MigrationCLI:
console.print(f"[bold red]Fluxer validation failed with error: {e}[/bold red]") console.print(f"[bold red]Fluxer validation failed with error: {e}[/bold red]")
else: else:
console.print("[bold red]Fluxer bot token validation timed out after 10 seconds.[/bold red]") console.print("[bold red]Fluxer bot token validation timed out after 10 seconds.[/bold red]")
self.validation_results["fluxer_timeout"] = True
fluxer_task.cancel() fluxer_task.cancel()
# Only tokens and server/community existence are strictly required for 'tokens_valid' # Only tokens and server/community existence are strictly required for 'tokens_valid'
@ -168,10 +171,10 @@ class MigrationCLI:
console.print("") console.print("")
console.print(Panel.fit("Fluxer Reaper", style="bold blue")) console.print(Panel.fit("Fluxer Reaper", style="bold blue"))
d_name = self.validation_results.get("discord_server_name") d_name = self.validation_results.get("discord_server_name")
d_display = f"[bold green]\"{d_name}\"[/bold green]" if d_name else "[bold red]NOT SET UP[/bold red]" d_display = f"[bold green]\"{d_name}\"[/bold green]" if d_name else ("[bold yellow]TIMEOUT ERROR[/bold yellow]" if self.validation_results.get("discord_timeout") else "[bold red]NOT SET UP[/bold red]")
f_name = self.validation_results.get("fluxer_community_name") f_name = self.validation_results.get("fluxer_community_name")
f_display = f"[bold green]\"{f_name}\"[/bold green]" if f_name else "[bold red]NOT SET UP[/bold red]" f_display = f"[bold green]\"{f_name}\"[/bold green]" if f_name else ("[bold yellow]TIMEOUT ERROR[/bold yellow]" if self.validation_results.get("fluxer_timeout") else "[bold red]NOT SET UP[/bold red]")
console.print(f"[bold cyan]Discord Server:[/bold cyan] {d_display}") console.print(f"[bold cyan]Discord Server:[/bold cyan] {d_display}")
console.print(f"[bold #4641D9]Fluxer Community:[/bold #4641D9] {f_display}") console.print(f"[bold #4641D9]Fluxer Community:[/bold #4641D9] {f_display}")
@ -190,7 +193,7 @@ class MigrationCLI:
val_status = "[bold green][VALID][/bold green]" val_status = "[bold green][VALID][/bold green]"
console.print(f"(6) Configuration {val_status}") console.print(f"(6) Configuration {val_status}")
console.print("(7) [bold red]Danger Zone[/bold red]") console.print("(7) [red]Danger Zone[/red]")
console.print("(Q) Exit") console.print("(Q) Exit")
@ -466,6 +469,13 @@ class MigrationCLI:
return return
elif sub_choice == "F": elif sub_choice == "F":
force = True force = True
else:
if not Confirm.ask("Clone roles?", default=True):
await self.engine.close_connections()
return
if not Confirm.ask("Are you sure?", default=True):
await self.engine.close_connections()
return
console.print("\n[bold green]Starting Role Migration...[/bold green]") console.print("\n[bold green]Starting Role Migration...[/bold green]")
with Progress( with Progress(