BookStack icon indicating copy to clipboard operation
BookStack copied to clipboard

Webhooks "send" based on book or shelf

Open tritanium73 opened this issue 1 year ago • 1 comments

Describe the feature you'd like

It would be great if you could send webhooks based on the source:

Book 1 -> webhook target a Book 2 -> webhook target b

or

Shelf x

  • Book 3
  • Book 4

to webhook target c

Describe the benefits this would bring to existing BookStack users

This would allow us to notify our MS Teams channels in a more granular manner so that only certain books go to certain MS Teams channels

Can the goal of this request already be achieved via other means?

Not really, because when I create the webhook I unfortunately can't specify a source that the webhook end should refer to

Have you searched for an existing open/closed issue?

  • [X] I have searched for existing issues and none cover my fundamental request

How long have you been using BookStack?

3 months to 1 year

Additional context

No response

tritanium73 avatar May 14 '24 14:05 tritanium73

I'd suggest the theme system, but I don't think the webhook event covers it:

public function __construct(Webhook $webhook, string $event, Loggable|string $detail)
{
    $this->webhook = $webhook;
    $this->initiator = user();
    $this->initiatedTime = time();

    $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime);
    $this->webhookData =  $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format();
}

As far as I can tell, it can't change the webhook's data, only change the data that gets sent. It would be weird if you could change endpoints in place. The only other option would be to make a different webhook for each channel you want, and then set the output to empty if the event is for a book you don't care about for that endpoint:

<?php

use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Tools\WebhookFormatter;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;

function selectiveFormat(array $defaultWebhookData): array
{
    $book_id = $defaultWebhookData['related_item']['book_id'];
    $webhook_name = $defaultWebhookData['webhook_name']
    if ( !(
        ($book_id == 1 && $webhook_name == "Team 1 Webhook") ||
        ($book_id == 2 && $webhook_name == "Team 2 Webhook") ||
        ($book_id == 3 && $webhook_name == "Team 3 Webhook") ||
        ($book_id == 4 && $webhook_name == "Team 4 Webhook")
    )
    ) {
        // If it's a teams webhook, but the name and book ID don't match, then just return nothing and cause a 400 error.
        return [];
    }
    return null;

}

Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, function (
    string $event,
    Webhook $webhook,
    string|Loggable $detail,
    User $initiator,
    int $initTime,
) {
    if (str_starts_with($webhook->endpoint, 'https://teams.webhook.whatever')) {
        $defaultData = WebhookFormatter::getDefault($event, $webhook, $detail, $initiator, $initTime);
        return selectiveFormat($defaultData->format());
    }

    // Otherwise return null to leave the webhook data alone
    return null;
});

This will return [] when a book ID and webhook name according to your manual specifications don't match, but will return the normal value when they do.

The main problem with this is that it scales pretty badly with each team added - if you have 5 teams, that's 5 events firing and 5 webhooks trying to send data per action the webhook is listening to.

This is pretty heavily adapted from this page where Dan Brown went over formatting webhooks differently for Pushover.

(Disclaimer: I haven't tested this code due to time constraints on my end, so there might be some issues with how it grabs the data from the webhook for the webhook name and book ID, but that should be easy enough to debug)

DanielGordonIT avatar May 21 '24 15:05 DanielGordonIT