diff options
| -rw-r--r-- | bot/constants.py | 2 | ||||
| -rw-r--r-- | bot/exts/moderation/metabase.py | 109 | ||||
| -rw-r--r-- | config-default.yml | 10 | 
3 files changed, 70 insertions, 51 deletions
| diff --git a/bot/constants.py b/bot/constants.py index 500803f33..12b5c02e5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -568,7 +568,7 @@ class Metabase(metaclass=YAMLGetter):      username: Optional[str]      password: Optional[str] -    url: str +    base_url: str      max_session_age: int diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index e9faf7240..3b454ab18 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -42,6 +42,25 @@ class Metabase(Cog):          self.init_task = self.bot.loop.create_task(self.init_cog()) +    async def cog_command_error(self, ctx: Context, error: Exception) -> None: +        """Handle ClientResponseError errors locally to invalidate token if needed.""" +        if not isinstance(error.original, ClientResponseError): +            return + +        if error.original.status == 403: +            # User doesn't have access to the given question +            log.warning(f"Failed to auth with Metabase for {error.original.url}.") +            await ctx.send(f":x: {ctx.author.mention} Failed to auth with Metabase for that question.") +        elif error.original.status == 404: +            await ctx.send(f":x: {ctx.author.mention} That question could not be found.") +        else: +            # User credentials are invalid, or the refresh failed. +            # Delete the expiry time, to force a refresh on next startup. +            await self.session_info.delete("session_expiry") +            log.exception("Session token is invalid or refresh failed.") +            await ctx.send(f":x: {ctx.author.mention} Session token is invalid or refresh failed.") +        error.handled = True +      async def init_cog(self) -> None:          """Initialise the metabase session."""          expiry_time = await self.session_info.get("session_expiry") @@ -65,7 +84,7 @@ class Metabase(Cog):              "username": MetabaseConfig.username,              "password": MetabaseConfig.password          } -        async with self.bot.http_session.post(f"{MetabaseConfig.url}/session", json=data) as resp: +        async with self.bot.http_session.post(f"{MetabaseConfig.base_url}/api/session", json=data) as resp:              json_data = await resp.json()              self.session_token = json_data.get("id") @@ -86,7 +105,7 @@ class Metabase(Cog):          """A group of commands for interacting with metabase."""          await ctx.send_help(ctx.command) -    @metabase_group.command(name="extract") +    @metabase_group.command(name="extract", aliases=("export",))      async def metabase_extract(          self,          ctx: Context, @@ -106,48 +125,50 @@ class Metabase(Cog):          Valid extensions are: csv and json.          """ -        async with ctx.typing(): - -            # Make sure we have a session token before running anything -            await self.init_task - -            url = f"{MetabaseConfig.url}/card/{question_id}/query/{extension}" -            try: -                async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: -                    if extension == "csv": -                        out = await resp.text(encoding="utf-8") -                        # Save the output for use with int e -                        self.exports[question_id] = list(csv.DictReader(StringIO(out))) - -                    elif extension == "json": -                        out = await resp.json(encoding="utf-8") -                        # Save the output for use with int e -                        self.exports[question_id] = out - -                        # Format it nicely for human eyes -                        out = json.dumps(out, indent=4, sort_keys=True) -            except ClientResponseError as e: -                if e.status == 403: -                    # User doesn't have access to the given question -                    log.warning(f"Failed to auth with Metabase for question {question_id}.") -                    await ctx.send(f":x: {ctx.author.mention} Failed to auth with Metabase for that question.") -                else: -                    # User credentials are invalid, or the refresh failed. -                    # Delete the expiry time, to force a refresh on next startup. -                    await self.session_info.delete("session_expiry") -                    log.exception("Session token is invalid or refresh failed.") -                    await ctx.send(f":x: {ctx.author.mention} Session token is invalid or refresh failed.") -                return - -            paste_link = await send_to_paste_service(out, extension=extension) -            if paste_link: -                message = f":+1: {ctx.author.mention} Here's your link: {paste_link}" -            else: -                message = f":x: {ctx.author.mention} Link service is unavailible." -            await ctx.send( -                f"{message}\nYou can also access this data within internal eval by doing: " -                f"`bot.get_cog('Metabase').exports[{question_id}]`" -            ) +        await ctx.trigger_typing() + +        # Make sure we have a session token before running anything +        await self.init_task + +        url = f"{MetabaseConfig.base_url}/api/card/{question_id}/query/{extension}" + +        async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: +            if extension == "csv": +                out = await resp.text(encoding="utf-8") +                # Save the output for use with int e +                self.exports[question_id] = list(csv.DictReader(StringIO(out))) + +            elif extension == "json": +                out = await resp.json(encoding="utf-8") +                # Save the output for use with int e +                self.exports[question_id] = out + +                # Format it nicely for human eyes +                out = json.dumps(out, indent=4, sort_keys=True) + +        paste_link = await send_to_paste_service(out, extension=extension) +        if paste_link: +            message = f":+1: {ctx.author.mention} Here's your link: {paste_link}" +        else: +            message = f":x: {ctx.author.mention} Link service is unavailible." +        await ctx.send( +            f"{message}\nYou can also access this data within internal eval by doing: " +            f"`bot.get_cog('Metabase').exports[{question_id}]`" +        ) + +    @metabase_group.command(name="publish", aliases=("share",)) +    async def metabase_publish(self, ctx: Context, question_id: int) -> None: +        """Publically shares the given question and posts the link.""" +        await ctx.trigger_typing() +        # Make sure we have a session token before running anything +        await self.init_task + +        url = f"{MetabaseConfig.base_url}/api/card/{question_id}/public_link" + +        async with self.bot.http_session.post(url, headers=self.headers, raise_for_status=True) as resp: +            response_json = await resp.json(encoding="utf-8") +            sharing_url = f"{MetabaseConfig.base_url}/public/question/{response_json['uuid']}" +            await ctx.send(f":+1: {ctx.author.mention} Here's your sharing link: {sharing_url}")      # This cannot be static (must have a __func__ attribute).      async def cog_check(self, ctx: Context) -> bool: diff --git a/config-default.yml b/config-default.yml index 881a7df76..79828dd77 100644 --- a/config-default.yml +++ b/config-default.yml @@ -432,14 +432,12 @@ anti_spam:              max: 3 -  metabase: -    username: !ENV "METABASE_USERNAME" -    password: !ENV "METABASE_PASSWORD" -    url: "http://metabase.default.svc.cluster.local/api" +    username: !ENV      "METABASE_USERNAME" +    password: !ENV      "METABASE_PASSWORD" +    base_url:           "http://metabase.default.svc.cluster.local"      # 14 days, see https://www.metabase.com/docs/latest/operations-guide/environment-variables.html#max_session_age -    max_session_age: 20160 - +    max_session_age:    20160  big_brother: | 
