diff --git a/ctfeed.py b/ctfeed.py index 56203b0..d25f8aa 100644 --- a/ctfeed.py +++ b/ctfeed.py @@ -11,6 +11,7 @@ from src.database import database from src.backend.config import update_config_cache from src.utils import ctf_api +from src.utils import commit_id from src import crud from src import schema from src import bot @@ -23,6 +24,9 @@ @asynccontextmanager async def lifespan(app:FastAPI): # startup + ## get commit id + settings.COMMIT_ID = await commit_id.get_commit_id() + ## initialize database try: await database.init_db() @@ -100,3 +104,11 @@ async def index() -> schema.General: success=True, message="Shirakami Fubuki is the cutest fox in the world!" ) + + +@app.get("/version", tags=["Metadata"]) +async def version() -> schema.General: + return schema.General( + success=True, + message=settings.COMMIT_ID + ) \ No newline at end of file diff --git a/src/cog/ctfmenu.py b/src/cog/ctfmenu.py index f3fa935..f002e95 100644 --- a/src/cog/ctfmenu.py +++ b/src/cog/ctfmenu.py @@ -163,6 +163,7 @@ async def build_embed_and_view(self) -> discord.Embed: lines = [] for idx, e in enumerate(current, start=display_start + 1): channel_created = "[⭐️ Channel created]" if e.channel_id is not None else "" + users_count = len(e.users) if self.type == "ctftime": time_now = int(datetime.now(timezone.utc).timestamp()) @@ -173,11 +174,13 @@ async def build_embed_and_view(self) -> discord.Embed: lines.append(f"{channel_created}{now_running}") lines.append(f"Start: ()") lines.append(f"End: ()") + lines.append(f"Participants: {users_count}") lines.append(f"") else: lines.append(f"**[ID: {e.id}] {e.title}**") if len(channel_created) != 0: lines.append(f"{channel_created}") + lines.append(f"Participants: {users_count}") lines.append("") description = "\n".join(lines) @@ -287,6 +290,20 @@ async def _check_permission(self, interaction: discord.Interaction, force_pm:boo return member + async def _check_administrator_permission(self, interaction: discord.Interaction) -> Optional[discord.Member]: + if await security.discord_check_administrator(interaction) is False: + return None + + member = interaction.user + if isinstance(member, discord.Member) is False: + return None + + if member.id != self.owner_id: + await interaction.response.send_message("You are not the owner of this view", ephemeral=True) + return None + return member + + async def _read_event(self) -> Optional[model.Event]: try: async with database.with_get_db() as session: @@ -331,21 +348,29 @@ async def build_embed_and_view(self) -> discord.Embed: # build view pm_member = None + admin_member = None try: pm_member = await security.check_user(self.owner_id, True) except Exception: pass + try: + admin_member = await security.check_administrator(self.owner_id) + except Exception: + pass if pm_member: - if self.relink_channel not in self.children: - self.add_item(self.relink_channel) if self.archive_event not in self.children: self.add_item(self.archive_event) else: - if self.relink_channel in self.children: - self.remove_item(self.relink_channel) if self.archive_event in self.children: self.remove_item(self.archive_event) + if admin_member: + if self.relink_channel not in self.children: + self.add_item(self.relink_channel) + else: + if self.relink_channel in self.children: + self.remove_item(self.relink_channel) + return embed @@ -403,7 +428,7 @@ async def archive_event(self, button: discord.ui.Button, interaction: discord.In ) async def relink_channel(self, select: discord.ui.Select, interaction: discord.Interaction): # check permission - if await self._check_permission(interaction, True) is None: + if await self._check_administrator_permission(interaction) is None: return # argument check diff --git a/src/cog/help.py b/src/cog/help.py index 69be971..3ca4b70 100644 --- a/src/cog/help.py +++ b/src/cog/help.py @@ -55,6 +55,7 @@ async def build_embed_and_view(self) -> discord.Embed: value="\n".join(commands_info), inline=False ) + embed.set_footer(text=f"Commit: {settings.COMMIT_ID}") return embed diff --git a/src/config.py b/src/config.py index 096cc71..42c83da 100644 --- a/src/config.py +++ b/src/config.py @@ -35,6 +35,9 @@ class Settings(BaseSettings): # Database configuration DATABASE_URL:str + + # Metadata + COMMIT_ID:str="unknown" model_config = SettingsConfigDict(env_file=".env") diff --git a/src/router/ctf.py b/src/router/ctf.py index 482f293..5b7d217 100644 --- a/src/router/ctf.py +++ b/src/router/ctf.py @@ -160,7 +160,7 @@ async def archive_event( async def relink_event( event_db_id:int, data:schema.RelinkEvent, - member:discord.Member=Depends(security.fastapi_check_pm_user) + member:discord.Member=Depends(security.fastapi_check_administrator) ) -> schema.General: try: await channel_op.link_event_to_channel(event_db_id, data.channel_id) diff --git a/src/utils/commit_id.py b/src/utils/commit_id.py new file mode 100644 index 0000000..c641aa4 --- /dev/null +++ b/src/utils/commit_id.py @@ -0,0 +1,30 @@ +import asyncio +import logging + +# logging +logger = logging.getLogger("uvicorn") + + +# functions +async def get_commit_id(timeout_sec: float = 3.0) -> str: + try: + process = await asyncio.create_subprocess_exec( + "git", + "rev-parse", + "--short", + "HEAD", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout_sec) + if process.returncode != 0: + logger.error(f"fail to read commit id: {stderr.decode(errors='replace').strip()}") + return "unknown" + + commit_id = stdout.decode(errors="replace").strip() + if len(commit_id) == 0: + return "unknown" + return commit_id + except Exception as e: + logger.error(f"fail to read commit id: {str(e)}") + return "unknown"