mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-17 14:37:07 +00:00
Add Mastodon webhook; refactor Webhook dispatching.
This commit is contained in:
parent
493423c6bf
commit
33a1c84b5a
|
@ -5,6 +5,8 @@ release channel, you can take advantage of these new features and fixes.
|
||||||
|
|
||||||
## New Features/Changes
|
## New Features/Changes
|
||||||
|
|
||||||
|
- **Mastodon Posting Support**: Publish to Mastodon via a Web Hook, the same way you do with Twitter!
|
||||||
|
|
||||||
- **Cover Art Files Support**: Many users keep the cover art for their media alongside the media in a separate image
|
- **Cover Art Files Support**: Many users keep the cover art for their media alongside the media in a separate image
|
||||||
file. AzuraCast now detects image files in the same folder as your media and uses it as the default album art for that
|
file. AzuraCast now detects image files in the same folder as your media and uses it as the default album art for that
|
||||||
media. Because cover art files are often named a variety of things, we currently will use _any_ image file that exists
|
media. Because cover art files are often named a variety of things, we currently will use _any_ image file that exists
|
||||||
|
@ -20,6 +22,8 @@ release channel, you can take advantage of these new features and fixes.
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fixed an issue where listener connection times over a day didn't properly show up.
|
||||||
|
|
||||||
- Fixed several issues contributing to slow load times on media manager pages.
|
- Fixed several issues contributing to slow load times on media manager pages.
|
||||||
|
|
||||||
- Fixed a bug where if a station only had "Allowed IPs", it wouldn't be enforced.
|
- Fixed a bug where if a station only had "Allowed IPs", it wouldn't be enforced.
|
||||||
|
|
|
@ -61,6 +61,12 @@ return [
|
||||||
'description' => __('Automatically send a tweet.'),
|
'description' => __('Automatically send a tweet.'),
|
||||||
'triggers' => $allTriggers,
|
'triggers' => $allTriggers,
|
||||||
],
|
],
|
||||||
|
Connector\Mastodon::NAME => [
|
||||||
|
'class' => Connector\Mastodon::class,
|
||||||
|
'name' => __('Mastodon Post'),
|
||||||
|
'description' => __('Automatically publish to a Mastodon instance.'),
|
||||||
|
'triggers' => [],
|
||||||
|
],
|
||||||
Connector\GoogleAnalytics::NAME => [
|
Connector\GoogleAnalytics::NAME => [
|
||||||
'class' => Connector\GoogleAnalytics::class,
|
'class' => Connector\GoogleAnalytics::class,
|
||||||
'name' => __('Google Analytics Integration'),
|
'name' => __('Google Analytics Integration'),
|
||||||
|
|
|
@ -24,14 +24,15 @@ import BaseEditModal from '~/components/Common/BaseEditModal';
|
||||||
import TypeSelect from "./Form/TypeSelect";
|
import TypeSelect from "./Form/TypeSelect";
|
||||||
import BasicInfo from "./Form/BasicInfo";
|
import BasicInfo from "./Form/BasicInfo";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Generic from "~/components/Stations/Webhooks/Form/Generic";
|
import Generic from "./Form/Generic";
|
||||||
import Email from "~/components/Stations/Webhooks/Form/Email";
|
import Email from "./Form/Email";
|
||||||
import Tunein from "~/components/Stations/Webhooks/Form/Tunein";
|
import Tunein from "./Form/Tunein";
|
||||||
import Discord from "~/components/Stations/Webhooks/Form/Discord";
|
import Discord from "./Form/Discord";
|
||||||
import Telegram from "~/components/Stations/Webhooks/Form/Telegram";
|
import Telegram from "./Form/Telegram";
|
||||||
import Twitter from "~/components/Stations/Webhooks/Form/Twitter";
|
import Twitter from "./Form/Twitter";
|
||||||
import GoogleAnalytics from "~/components/Stations/Webhooks/Form/GoogleAnalytics";
|
import GoogleAnalytics from "./Form/GoogleAnalytics";
|
||||||
import MatomoAnalytics from "~/components/Stations/Webhooks/Form/MatomoAnalytics";
|
import MatomoAnalytics from "./Form/MatomoAnalytics";
|
||||||
|
import Mastodon from "./Form/Mastodon";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditModal',
|
name: 'EditModal',
|
||||||
|
@ -193,6 +194,23 @@ export default {
|
||||||
message: this.langTwitterDefaultMessage
|
message: this.langTwitterDefaultMessage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'mastodon': {
|
||||||
|
component: Mastodon,
|
||||||
|
validations: {
|
||||||
|
instance_url: {required},
|
||||||
|
access_token: {required},
|
||||||
|
rate_limit: {},
|
||||||
|
message: {required},
|
||||||
|
visibility: {required}
|
||||||
|
},
|
||||||
|
defaultConfig: {
|
||||||
|
instance_url: '',
|
||||||
|
access_token: '',
|
||||||
|
rate_limit: 0,
|
||||||
|
message: this.langTwitterDefaultMessage,
|
||||||
|
visibility: 'public'
|
||||||
|
}
|
||||||
|
},
|
||||||
'google_analytics': {
|
'google_analytics': {
|
||||||
component: GoogleAnalytics,
|
component: GoogleAnalytics,
|
||||||
validations: {
|
validations: {
|
||||||
|
|
167
frontend/vue/components/Stations/Webhooks/Form/Mastodon.vue
Normal file
167
frontend/vue/components/Stations/Webhooks/Form/Mastodon.vue
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<b-form-group>
|
||||||
|
<template #label>
|
||||||
|
<translate key="lang_mastodon_hdr">Mastodon Account Details</translate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p class="card-text">
|
||||||
|
<translate key="lang_mastodon_instructions_1">Steps for configuring a Mastodon application:</translate>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<translate key="lang_mastodon_instructions_1">Visit your Mastodon instance.</translate>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<translate key="lang_mastodon_instructions_2">Click the "Preferences" link, then "Development" on the left side menu.</translate>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<translate key="lang_mastodon_instructions_3">Click "New Application"</translate>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<translate key="lang_mastodon_instructions_4">Enter "AzuraCast" as the application name. You can leave the URL fields unchanged. For "Scopes", only "write:media" and "write:statuses" are required.</translate>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p class="card-text">
|
||||||
|
<translate key="lang_twitter_instructions_5">Once these steps are completed, enter the "Access Token" from the application's page into the field below.</translate>
|
||||||
|
</p>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-form-group>
|
||||||
|
<b-form-row>
|
||||||
|
<b-wrapped-form-group class="col-md-6" id="form_config_instance_url" :field="form.config.instance_url">
|
||||||
|
<template #label="{lang}">
|
||||||
|
<translate :key="lang">Mastodon Instance URL</translate>
|
||||||
|
</template>
|
||||||
|
<template #description="{lang}">
|
||||||
|
<translate
|
||||||
|
:key="lang">If your Mastodon username is "@test@example.com", enter "example.com".</translate>
|
||||||
|
</template>
|
||||||
|
</b-wrapped-form-group>
|
||||||
|
|
||||||
|
<b-wrapped-form-group class="col-md-6" id="form_config_access_token"
|
||||||
|
:field="form.config.access_token">
|
||||||
|
<template #label="{lang}">
|
||||||
|
<translate :key="lang">Access Token</translate>
|
||||||
|
</template>
|
||||||
|
</b-wrapped-form-group>
|
||||||
|
|
||||||
|
<b-wrapped-form-group class="col-md-12" id="form_config_rate_limit" :field="form.config.rate_limit">
|
||||||
|
<template #label="{lang}">
|
||||||
|
<translate :key="lang">Only Post Once Every...</translate>
|
||||||
|
</template>
|
||||||
|
<template #default="props">
|
||||||
|
<b-form-radio-group stacked :id="props.id" :options="rateLimitOptions"
|
||||||
|
v-model="props.field.$model">
|
||||||
|
</b-form-radio-group>
|
||||||
|
</template>
|
||||||
|
</b-wrapped-form-group>
|
||||||
|
</b-form-row>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<common-formatting-info></common-formatting-info>
|
||||||
|
|
||||||
|
<b-form-group>
|
||||||
|
<b-form-row>
|
||||||
|
<b-wrapped-form-group class="col-md-12" id="form_config_message" :field="form.config.message"
|
||||||
|
input-type="textarea">
|
||||||
|
<template #label="{lang}">
|
||||||
|
<translate :key="lang">Message Body</translate>
|
||||||
|
</template>
|
||||||
|
</b-wrapped-form-group>
|
||||||
|
|
||||||
|
<b-wrapped-form-group class="col-md-12" id="form_config_visibility" :field="form.config.visibility">
|
||||||
|
<template #label="{lang}">
|
||||||
|
<translate :key="lang">Message Visibility</translate>
|
||||||
|
</template>
|
||||||
|
<template #default="props">
|
||||||
|
<b-form-radio-group stacked :id="props.id" :options="visibilityOptions"
|
||||||
|
v-model="props.field.$model">
|
||||||
|
</b-form-radio-group>
|
||||||
|
</template>
|
||||||
|
</b-wrapped-form-group>
|
||||||
|
</b-form-row>
|
||||||
|
</b-form-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
|
||||||
|
import CommonFormattingInfo from "./CommonFormattingInfo";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Twitter',
|
||||||
|
components: {CommonFormattingInfo, BWrappedFormGroup},
|
||||||
|
props: {
|
||||||
|
form: Object
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
langSeconds() {
|
||||||
|
return this.$gettext('%{ seconds } seconds');
|
||||||
|
},
|
||||||
|
langMinutes() {
|
||||||
|
return this.$gettext('%{ minutes } minutes');
|
||||||
|
},
|
||||||
|
rateLimitOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$gettext('No Limit'),
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langSeconds, {seconds: 15}),
|
||||||
|
value: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langSeconds, {seconds: 30}),
|
||||||
|
value: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langSeconds, {seconds: 60}),
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 2}),
|
||||||
|
value: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 5}),
|
||||||
|
value: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 10}),
|
||||||
|
value: 600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 15}),
|
||||||
|
value: 900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 30}),
|
||||||
|
value: 1800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettextInterpolate(this.langMinutes, {minutes: 60}),
|
||||||
|
value: 3600,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
visibilityOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$gettext('Public'),
|
||||||
|
value: 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettext('Unlisted'),
|
||||||
|
value: 'unlisted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$gettext('Private'),
|
||||||
|
value: 'private',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -114,4 +114,14 @@ abstract class AbstractConnector implements ConnectorInterface
|
||||||
$pattern = sprintf(UrlValidator::PATTERN, 'http|https');
|
$pattern = sprintf(UrlValidator::PATTERN, 'http|https');
|
||||||
return (preg_match($pattern, $url)) ? $url : null;
|
return (preg_match($pattern, $url)) ? $url : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function incompleteConfigException(): \InvalidArgumentException
|
||||||
|
{
|
||||||
|
return new \InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'Webhook %s is missing necessary configuration. Skipping...',
|
||||||
|
static::NAME
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,11 @@ interface ConnectorInterface
|
||||||
* @param Entity\StationWebhook $webhook
|
* @param Entity\StationWebhook $webhook
|
||||||
* @param Entity\Api\NowPlaying\NowPlaying $np
|
* @param Entity\Api\NowPlaying\NowPlaying $np
|
||||||
* @param array<string> $triggers
|
* @param array<string> $triggers
|
||||||
*
|
|
||||||
* @return bool Whether the webhook actually dispatched.
|
|
||||||
*/
|
*/
|
||||||
public function dispatch(
|
public function dispatch(
|
||||||
Entity\Station $station,
|
Entity\Station $station,
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Webhook\Connector;
|
namespace App\Webhook\Connector;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
use Monolog\Level;
|
use Monolog\Level;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -72,14 +71,13 @@ final class Discord extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
$webhook_url = $this->getValidUrl($config['webhook_url'] ?? '');
|
$webhook_url = $this->getValidUrl($config['webhook_url'] ?? '');
|
||||||
|
|
||||||
if (empty($webhook_url)) {
|
if (empty($webhook_url)) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$raw_vars = [
|
$raw_vars = [
|
||||||
|
@ -131,29 +129,22 @@ final class Discord extends AbstractConnector
|
||||||
// Dispatch webhook
|
// Dispatch webhook
|
||||||
$this->logger->debug('Dispatching Discord webhook...');
|
$this->logger->debug('Dispatching Discord webhook...');
|
||||||
|
|
||||||
try {
|
$response = $this->httpClient->request(
|
||||||
$response = $this->httpClient->request(
|
'POST',
|
||||||
'POST',
|
$webhook_url,
|
||||||
$webhook_url,
|
[
|
||||||
[
|
'headers' => [
|
||||||
'headers' => [
|
'Content-Type' => 'application/json',
|
||||||
'Content-Type' => 'application/json',
|
],
|
||||||
],
|
'json' => $webhook_body,
|
||||||
'json' => $webhook_body,
|
]
|
||||||
]
|
);
|
||||||
);
|
|
||||||
|
|
||||||
$this->logger->addRecord(
|
$this->logger->addRecord(
|
||||||
($response->getStatusCode() !== 204 ? Level::Error : Level::Debug),
|
($response->getStatusCode() !== 204 ? Level::Error : Level::Debug),
|
||||||
sprintf('Webhook %s returned code %d', self::NAME, $response->getStatusCode()),
|
sprintf('Webhook %s returned code %d', self::NAME, $response->getStatusCode()),
|
||||||
['message_sent' => $webhook_body, 'response_body' => $response->getBody()->getContents()]
|
['message_sent' => $webhook_body, 'response_body' => $response->getBody()->getContents()]
|
||||||
);
|
);
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(sprintf('Error from Discord (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection HttpUrlsUsage */
|
/** @noinspection HttpUrlsUsage */
|
||||||
|
|
|
@ -8,7 +8,6 @@ use App\Entity;
|
||||||
use App\Service\Mail;
|
use App\Service\Mail;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
|
||||||
|
|
||||||
final class Email extends AbstractConnector
|
final class Email extends AbstractConnector
|
||||||
{
|
{
|
||||||
|
@ -30,10 +29,9 @@ final class Email extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
if (!$this->mail->isEnabled()) {
|
if (!$this->mail->isEnabled()) {
|
||||||
$this->logger->error('E-mail delivery is not currently enabled. Skipping webhook delivery...');
|
throw new \RuntimeException('E-mail delivery is not currently enabled. Skipping webhook delivery...');
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
@ -42,32 +40,24 @@ final class Email extends AbstractConnector
|
||||||
$emailBody = $config['message'];
|
$emailBody = $config['message'];
|
||||||
|
|
||||||
if (empty($emailTo) || empty($emailSubject) || empty($emailBody)) {
|
if (empty($emailTo) || empty($emailSubject) || empty($emailBody)) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$email = $this->mail->createMessage();
|
||||||
$email = $this->mail->createMessage();
|
|
||||||
|
|
||||||
foreach (explode(',', $emailTo) as $emailToPart) {
|
foreach (explode(',', $emailTo) as $emailToPart) {
|
||||||
$email->addTo(trim($emailToPart));
|
$email->addTo(trim($emailToPart));
|
||||||
}
|
|
||||||
|
|
||||||
$vars = [
|
|
||||||
'subject' => $emailSubject,
|
|
||||||
'body' => $emailBody,
|
|
||||||
];
|
|
||||||
$vars = $this->replaceVariables($vars, $np);
|
|
||||||
|
|
||||||
$email->subject($vars['subject']);
|
|
||||||
$email->text($vars['body']);
|
|
||||||
|
|
||||||
$this->mail->send($email);
|
|
||||||
} catch (TransportExceptionInterface $e) {
|
|
||||||
$this->logger->error(sprintf('Error from e-mail (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
$vars = [
|
||||||
|
'subject' => $emailSubject,
|
||||||
|
'body' => $emailBody,
|
||||||
|
];
|
||||||
|
$vars = $this->replaceVariables($vars, $np);
|
||||||
|
|
||||||
|
$email->subject($vars['subject']);
|
||||||
|
$email->text($vars['body']);
|
||||||
|
|
||||||
|
$this->mail->send($email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Webhook\Connector;
|
namespace App\Webhook\Connector;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
|
|
||||||
final class Generic extends AbstractConnector
|
final class Generic extends AbstractConnector
|
||||||
{
|
{
|
||||||
|
@ -19,43 +18,35 @@ final class Generic extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
$webhook_url = $this->getValidUrl($config['webhook_url'] ?? '');
|
$webhook_url = $this->getValidUrl($config['webhook_url'] ?? '');
|
||||||
|
|
||||||
if (empty($webhook_url)) {
|
if (empty($webhook_url)) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$request_options = [
|
||||||
$request_options = [
|
'headers' => [
|
||||||
'headers' => [
|
'Content-Type' => 'application/json',
|
||||||
'Content-Type' => 'application/json',
|
],
|
||||||
],
|
'json' => $np,
|
||||||
'json' => $np,
|
'timeout' => (float)($config['timeout'] ?? 5.0),
|
||||||
'timeout' => (float)($config['timeout'] ?? 5.0),
|
];
|
||||||
|
|
||||||
|
if (!empty($config['basic_auth_username']) && !empty($config['basic_auth_password'])) {
|
||||||
|
$request_options['auth'] = [
|
||||||
|
$config['basic_auth_username'],
|
||||||
|
$config['basic_auth_password'],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!empty($config['basic_auth_username']) && !empty($config['basic_auth_password'])) {
|
|
||||||
$request_options['auth'] = [
|
|
||||||
$config['basic_auth_username'],
|
|
||||||
$config['basic_auth_password'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $this->httpClient->request('POST', $webhook_url, $request_options);
|
|
||||||
|
|
||||||
$this->logger->debug(
|
|
||||||
sprintf('Generic webhook returned code %d', $response->getStatusCode()),
|
|
||||||
['response_body' => $response->getBody()->getContents()]
|
|
||||||
);
|
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(sprintf('Error from generic webhook (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
$response = $this->httpClient->request('POST', $webhook_url, $request_options);
|
||||||
|
|
||||||
|
$this->logger->debug(
|
||||||
|
sprintf('Generic webhook returned code %d', $response->getStatusCode()),
|
||||||
|
['response_body' => $response->getBody()->getContents()]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,10 @@ final class GoogleAnalytics extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
if (empty($config['tracking_id'])) {
|
if (empty($config['tracking_id'])) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get listen URLs for each mount point.
|
// Get listen URLs for each mount point.
|
||||||
|
@ -95,7 +94,5 @@ final class GoogleAnalytics extends AbstractConnector
|
||||||
}
|
}
|
||||||
|
|
||||||
$analytics->sendEnqueuedHits();
|
$analytics->sendEnqueuedHits();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
73
src/Webhook/Connector/Mastodon.php
Normal file
73
src/Webhook/Connector/Mastodon.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Webhook\Connector;
|
||||||
|
|
||||||
|
use App\Entity;
|
||||||
|
use App\Utilities\Urls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mastodon web hook connector.
|
||||||
|
*/
|
||||||
|
final class Mastodon extends AbstractConnector
|
||||||
|
{
|
||||||
|
public const NAME = 'mastodon';
|
||||||
|
|
||||||
|
protected function getRateLimitTime(Entity\StationWebhook $webhook): ?int
|
||||||
|
{
|
||||||
|
$config = $webhook->getConfig();
|
||||||
|
$rateLimitSeconds = (int)($config['rate_limit'] ?? 0);
|
||||||
|
|
||||||
|
return max(10, $rateLimitSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(
|
||||||
|
Entity\Station $station,
|
||||||
|
Entity\StationWebhook $webhook,
|
||||||
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
|
array $triggers
|
||||||
|
): void {
|
||||||
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
|
$instanceUrl = trim($config['instance_url'] ?? '');
|
||||||
|
$accessToken = trim($config['access_token'] ?? '');
|
||||||
|
|
||||||
|
if (empty($instanceUrl) || empty($accessToken)) {
|
||||||
|
throw $this->incompleteConfigException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages = $this->replaceVariables(
|
||||||
|
[
|
||||||
|
'message' => $config['message'] ?? '',
|
||||||
|
],
|
||||||
|
$np
|
||||||
|
);
|
||||||
|
|
||||||
|
$instanceUri = Urls::parseUserUrl($instanceUrl, 'Mastodon Instance URL');
|
||||||
|
$visibility = $config['visibility'] ?? 'public';
|
||||||
|
|
||||||
|
$response = $this->httpClient->request(
|
||||||
|
'POST',
|
||||||
|
$instanceUri->withPath('/api/v1/statuses'),
|
||||||
|
[
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $accessToken,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'json' => [
|
||||||
|
'status' => $messages['message'],
|
||||||
|
'visibility' => $visibility,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->debug(
|
||||||
|
sprintf('Webhook %s returned code %d', self::NAME, $response->getStatusCode()),
|
||||||
|
[
|
||||||
|
'instanceUri' => (string)$instanceUri,
|
||||||
|
'response' => $response->getBody()->getContents(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ use App\Entity;
|
||||||
use App\Http\RouterInterface;
|
use App\Http\RouterInterface;
|
||||||
use App\Utilities\Urls;
|
use App\Utilities\Urls;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Psr\Http\Message\UriInterface;
|
use Psr\Http\Message\UriInterface;
|
||||||
|
|
||||||
|
@ -33,12 +32,11 @@ final class MatomoAnalytics extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
if (empty($config['matomo_url']) || empty($config['site_id'])) {
|
if (empty($config['matomo_url']) || empty($config['site_id'])) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get listen URLs for each mount point.
|
// Get listen URLs for each mount point.
|
||||||
|
@ -119,21 +117,19 @@ final class MatomoAnalytics extends AbstractConnector
|
||||||
$i++;
|
$i++;
|
||||||
|
|
||||||
if (100 === $i) {
|
if (100 === $i) {
|
||||||
if (!$this->sendBatch($apiUrl, $apiToken, $entries)) {
|
$this->sendBatch($apiUrl, $apiToken, $entries);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$entries = [];
|
$entries = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->sendBatch($apiUrl, $apiToken, $entries);
|
$this->sendBatch($apiUrl, $apiToken, $entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendBatch(UriInterface $apiUrl, ?string $apiToken, array $entries): bool
|
private function sendBatch(UriInterface $apiUrl, ?string $apiToken, array $entries): void
|
||||||
{
|
{
|
||||||
if (empty($entries)) {
|
if (empty($entries)) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$jsonBody = [
|
$jsonBody = [
|
||||||
|
@ -148,20 +144,13 @@ final class MatomoAnalytics extends AbstractConnector
|
||||||
|
|
||||||
$this->logger->debug('Message body for Matomo API Query', ['body' => $jsonBody]);
|
$this->logger->debug('Message body for Matomo API Query', ['body' => $jsonBody]);
|
||||||
|
|
||||||
try {
|
$response = $this->httpClient->post($apiUrl, [
|
||||||
$response = $this->httpClient->post($apiUrl, [
|
'json' => $jsonBody,
|
||||||
'json' => $jsonBody,
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
$this->logger->debug(
|
$this->logger->debug(
|
||||||
sprintf('Matomo returned code %d', $response->getStatusCode()),
|
sprintf('Matomo returned code %d', $response->getStatusCode()),
|
||||||
['response_body' => $response->getBody()->getContents()]
|
['response_body' => $response->getBody()->getContents()]
|
||||||
);
|
);
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(sprintf('Error from Matomo (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Webhook\Connector;
|
namespace App\Webhook\Connector;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Telegram web hook connector.
|
* Telegram web hook connector.
|
||||||
|
@ -24,15 +23,14 @@ final class Telegram extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
$bot_token = trim($config['bot_token'] ?? '');
|
$bot_token = trim($config['bot_token'] ?? '');
|
||||||
$chat_id = trim($config['chat_id'] ?? '');
|
$chat_id = trim($config['chat_id'] ?? '');
|
||||||
|
|
||||||
if (empty($bot_token) || empty($chat_id)) {
|
if (empty($bot_token) || empty($chat_id)) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages = $this->replaceVariables(
|
$messages = $this->replaceVariables(
|
||||||
|
@ -42,48 +40,33 @@ final class Telegram extends AbstractConnector
|
||||||
$np
|
$np
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
$api_url = (!empty($config['api'])) ? rtrim($config['api'], '/') : 'https://api.telegram.org';
|
||||||
$api_url = (!empty($config['api'])) ? rtrim($config['api'], '/') : 'https://api.telegram.org';
|
$webhook_url = $api_url . '/bot' . $bot_token . '/sendMessage';
|
||||||
$webhook_url = $api_url . '/bot' . $bot_token . '/sendMessage';
|
|
||||||
|
|
||||||
$request_params = [
|
$request_params = [
|
||||||
'chat_id' => $chat_id,
|
'chat_id' => $chat_id,
|
||||||
'text' => $messages['text'],
|
'text' => $messages['text'],
|
||||||
'parse_mode' => $config['parse_mode'] ?? 'Markdown', // Markdown or HTML
|
'parse_mode' => $config['parse_mode'] ?? 'Markdown', // Markdown or HTML
|
||||||
];
|
];
|
||||||
|
|
||||||
$response = $this->httpClient->request(
|
$response = $this->httpClient->request(
|
||||||
'POST',
|
'POST',
|
||||||
$webhook_url,
|
$webhook_url,
|
||||||
[
|
[
|
||||||
'headers' => [
|
'headers' => [
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
],
|
],
|
||||||
'json' => $request_params,
|
'json' => $request_params,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->logger->debug(
|
$this->logger->debug(
|
||||||
sprintf('Webhook %s returned code %d', self::NAME, $response->getStatusCode()),
|
sprintf('Webhook %s returned code %d', self::NAME, $response->getStatusCode()),
|
||||||
[
|
[
|
||||||
'request_url' => $webhook_url,
|
'request_url' => $webhook_url,
|
||||||
'request_params' => $request_params,
|
'request_params' => $request_params,
|
||||||
'response_body' => $response->getBody()->getContents(),
|
'response_body' => $response->getBody()->getContents(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(
|
|
||||||
sprintf(
|
|
||||||
'Error from webhook %s (%d): %s',
|
|
||||||
self::NAME,
|
|
||||||
$e->getCode(),
|
|
||||||
$e->getMessage()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Webhook\Connector;
|
namespace App\Webhook\Connector;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
|
|
||||||
final class TuneIn extends AbstractConnector
|
final class TuneIn extends AbstractConnector
|
||||||
{
|
{
|
||||||
|
@ -24,40 +23,32 @@ final class TuneIn extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
if (empty($config['partner_id']) || empty($config['partner_key']) || empty($config['station_id'])) {
|
if (empty($config['partner_id']) || empty($config['partner_key']) || empty($config['station_id'])) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger->debug('Dispatching TuneIn AIR API call...');
|
$this->logger->debug('Dispatching TuneIn AIR API call...');
|
||||||
|
|
||||||
try {
|
$response = $this->httpClient->get(
|
||||||
$response = $this->httpClient->get(
|
'https://air.radiotime.com/Playing.ashx',
|
||||||
'https://air.radiotime.com/Playing.ashx',
|
[
|
||||||
[
|
'query' => [
|
||||||
'query' => [
|
'partnerId' => $config['partner_id'],
|
||||||
'partnerId' => $config['partner_id'],
|
'partnerKey' => $config['partner_key'],
|
||||||
'partnerKey' => $config['partner_key'],
|
'id' => $config['station_id'],
|
||||||
'id' => $config['station_id'],
|
'title' => $np->now_playing?->song?->title,
|
||||||
'title' => $np->now_playing?->song?->title,
|
'artist' => $np->now_playing?->song?->artist,
|
||||||
'artist' => $np->now_playing?->song?->artist,
|
'album' => $np->now_playing?->song?->album,
|
||||||
'album' => $np->now_playing?->song?->album,
|
],
|
||||||
],
|
]
|
||||||
]
|
);
|
||||||
);
|
|
||||||
|
|
||||||
$this->logger->debug(
|
$this->logger->debug(
|
||||||
sprintf('TuneIn returned code %d', $response->getStatusCode()),
|
sprintf('TuneIn returned code %d', $response->getStatusCode()),
|
||||||
['response_body' => $response->getBody()->getContents()]
|
['response_body' => $response->getBody()->getContents()]
|
||||||
);
|
);
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(sprintf('Error from TuneIn (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ namespace App\Webhook\Connector;
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Service\GuzzleFactory;
|
use App\Service\GuzzleFactory;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\TransferException;
|
|
||||||
use GuzzleHttp\Subscriber\Oauth\Oauth1;
|
use GuzzleHttp\Subscriber\Oauth\Oauth1;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ final class Twitter extends AbstractConnector
|
||||||
Entity\StationWebhook $webhook,
|
Entity\StationWebhook $webhook,
|
||||||
Entity\Api\NowPlaying\NowPlaying $np,
|
Entity\Api\NowPlaying\NowPlaying $np,
|
||||||
array $triggers
|
array $triggers
|
||||||
): bool {
|
): void {
|
||||||
$config = $webhook->getConfig();
|
$config = $webhook->getConfig();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -48,8 +47,7 @@ final class Twitter extends AbstractConnector
|
||||||
|| empty($config['token'])
|
|| empty($config['token'])
|
||||||
|| empty($config['token_secret'])
|
|| empty($config['token_secret'])
|
||||||
) {
|
) {
|
||||||
$this->logger->error('Webhook ' . self::NAME . ' is missing necessary configuration. Skipping...');
|
throw $this->incompleteConfigException();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up Twitter OAuth
|
// Set up Twitter OAuth
|
||||||
|
@ -74,28 +72,21 @@ final class Twitter extends AbstractConnector
|
||||||
// Dispatch webhook
|
// Dispatch webhook
|
||||||
$this->logger->debug('Posting to Twitter...');
|
$this->logger->debug('Posting to Twitter...');
|
||||||
|
|
||||||
try {
|
$response = $this->httpClient->request(
|
||||||
$response = $this->httpClient->request(
|
'POST',
|
||||||
'POST',
|
'https://api.twitter.com/1.1/statuses/update.json',
|
||||||
'https://api.twitter.com/1.1/statuses/update.json',
|
[
|
||||||
[
|
'auth' => 'oauth',
|
||||||
'auth' => 'oauth',
|
'handler' => $stack,
|
||||||
'handler' => $stack,
|
'form_params' => [
|
||||||
'form_params' => [
|
'status' => $vars['message'],
|
||||||
'status' => $vars['message'],
|
],
|
||||||
],
|
]
|
||||||
]
|
);
|
||||||
);
|
|
||||||
|
|
||||||
$this->logger->debug(
|
$this->logger->debug(
|
||||||
sprintf('Twitter returned code %d', $response->getStatusCode()),
|
sprintf('Twitter returned code %d', $response->getStatusCode()),
|
||||||
['response_body' => $response->getBody()->getContents()]
|
['response_body' => $response->getBody()->getContents()]
|
||||||
);
|
);
|
||||||
} catch (TransferException $e) {
|
|
||||||
$this->logger->error(sprintf('Error from Twitter (%d): %s', $e->getCode(), $e->getMessage()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,12 @@ namespace App\Webhook;
|
||||||
|
|
||||||
use App\Entity;
|
use App\Entity;
|
||||||
use App\Environment;
|
use App\Environment;
|
||||||
use App\Exception;
|
|
||||||
use App\Http\RouterInterface;
|
use App\Http\RouterInterface;
|
||||||
use App\Message;
|
use App\Message;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Level;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
|
|
||||||
final class Dispatcher
|
final class Dispatcher
|
||||||
{
|
{
|
||||||
|
@ -36,88 +34,108 @@ final class Dispatcher
|
||||||
public function __invoke(Message\AbstractMessage $message): void
|
public function __invoke(Message\AbstractMessage $message): void
|
||||||
{
|
{
|
||||||
if ($message instanceof Message\DispatchWebhookMessage) {
|
if ($message instanceof Message\DispatchWebhookMessage) {
|
||||||
$station = $this->em->find(Entity\Station::class, $message->station_id);
|
$this->handleDispatch($message);
|
||||||
if (!$station instanceof Entity\Station) {
|
} elseif ($message instanceof Message\TestWebhookMessage) {
|
||||||
return;
|
$this->testDispatch($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleDispatch(Message\DispatchWebhookMessage $message): void
|
||||||
|
{
|
||||||
|
$station = $this->em->find(Entity\Station::class, $message->station_id);
|
||||||
|
if (!$station instanceof Entity\Station) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$np = $message->np;
|
||||||
|
$triggers = $message->triggers;
|
||||||
|
|
||||||
|
// Always dispatch the special "local" updater task.
|
||||||
|
$this->localHandler->dispatch($station, $np);
|
||||||
|
|
||||||
|
if ($this->environment->isTesting()) {
|
||||||
|
$this->logger->notice('In testing mode; no webhooks dispatched.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Entity\StationWebhook[] $enabledWebhooks */
|
||||||
|
$enabledWebhooks = $station->getWebhooks()->filter(
|
||||||
|
function (Entity\StationWebhook $webhook) {
|
||||||
|
return $webhook->getIsEnabled();
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$np = $message->np;
|
$this->logger->debug('Webhook dispatch: triggering events: ' . implode(', ', $triggers));
|
||||||
$triggers = (array)$message->triggers;
|
|
||||||
|
|
||||||
// Always dispatch the special "local" updater task.
|
foreach ($enabledWebhooks as $webhook) {
|
||||||
$this->localHandler->dispatch($station, $np);
|
$connectorObj = $this->connectors->getConnector($webhook->getType());
|
||||||
|
|
||||||
if ($this->environment->isTesting()) {
|
if ($connectorObj->shouldDispatch($webhook, $triggers)) {
|
||||||
$this->logger->notice('In testing mode; no webhooks dispatched.');
|
$this->logger->debug(sprintf('Dispatching connector "%s".', $webhook->getType()));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Entity\StationWebhook[] $enabledWebhooks */
|
|
||||||
$enabledWebhooks = $station->getWebhooks()->filter(
|
|
||||||
function (Entity\StationWebhook $webhook) {
|
|
||||||
return $webhook->getIsEnabled();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->logger->debug('Webhook dispatch: triggering events: ' . implode(', ', $triggers));
|
|
||||||
|
|
||||||
foreach ($enabledWebhooks as $webhook) {
|
|
||||||
$connectorObj = $this->connectors->getConnector($webhook->getType());
|
|
||||||
|
|
||||||
if ($connectorObj->shouldDispatch($webhook, $triggers)) {
|
|
||||||
$this->logger->debug(sprintf('Dispatching connector "%s".', $webhook->getType()));
|
|
||||||
|
|
||||||
|
try {
|
||||||
if ($connectorObj->dispatch($station, $webhook, $np, $triggers)) {
|
if ($connectorObj->dispatch($station, $webhook, $np, $triggers)) {
|
||||||
$webhook->updateLastSentTimestamp();
|
$webhook->updateLastSentTimestamp();
|
||||||
$this->em->persist($webhook);
|
$this->em->persist($webhook);
|
||||||
$this->em->flush();
|
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error(
|
||||||
|
sprintf(
|
||||||
|
'%s L%d: %s',
|
||||||
|
$e->getFile(),
|
||||||
|
$e->getLine(),
|
||||||
|
$e->getMessage()
|
||||||
|
),
|
||||||
|
[
|
||||||
|
'exception' => $e,
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif ($message instanceof Message\TestWebhookMessage) {
|
}
|
||||||
$outputPath = $message->outputPath;
|
|
||||||
|
|
||||||
if (null !== $outputPath) {
|
$this->em->flush();
|
||||||
$logHandler = new StreamHandler($outputPath, LogLevel::DEBUG, true);
|
}
|
||||||
$this->logger->pushHandler($logHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private function testDispatch(
|
||||||
|
Message\TestWebhookMessage $message
|
||||||
|
): void {
|
||||||
|
$outputPath = $message->outputPath;
|
||||||
|
|
||||||
|
if (null !== $outputPath) {
|
||||||
|
$logHandler = new StreamHandler($outputPath, Level::Debug, true);
|
||||||
|
$this->logger->pushHandler($logHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$webhook = $this->em->find(Entity\StationWebhook::class, $message->webhookId);
|
$webhook = $this->em->find(Entity\StationWebhook::class, $message->webhookId);
|
||||||
if ($webhook instanceof Entity\StationWebhook) {
|
if (!($webhook instanceof Entity\StationWebhook)) {
|
||||||
$this->testDispatch($webhook);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$station = $webhook->getStation();
|
||||||
|
$np = $this->nowPlayingApiGen->currentOrEmpty($station);
|
||||||
|
$np->resolveUrls($this->router->getBaseUrl());
|
||||||
|
$np->cache = 'event';
|
||||||
|
|
||||||
|
$connectorObj = $this->connectors->getConnector($webhook->getType());
|
||||||
|
$connectorObj->dispatch($station, $webhook, $np, [Entity\StationWebhook::TRIGGER_ALL]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error(
|
||||||
|
sprintf(
|
||||||
|
'%s L%d: %s',
|
||||||
|
$e->getFile(),
|
||||||
|
$e->getLine(),
|
||||||
|
$e->getMessage()
|
||||||
|
),
|
||||||
|
[
|
||||||
|
'exception' => $e,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
if (null !== $outputPath) {
|
if (null !== $outputPath) {
|
||||||
$this->logger->popHandler();
|
$this->logger->popHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a "test" dispatch of the web hook, regardless of whether it is currently enabled, and
|
|
||||||
* return any logging information this yields.
|
|
||||||
*
|
|
||||||
* @param Entity\StationWebhook $webhook
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function testDispatch(
|
|
||||||
Entity\StationWebhook $webhook
|
|
||||||
): TestHandler {
|
|
||||||
$station = $webhook->getStation();
|
|
||||||
|
|
||||||
$handler = new TestHandler(LogLevel::DEBUG, true);
|
|
||||||
$this->logger->pushHandler($handler);
|
|
||||||
|
|
||||||
$np = $this->nowPlayingApiGen->currentOrEmpty($station);
|
|
||||||
$np->resolveUrls($this->router->getBaseUrl());
|
|
||||||
$np->cache = 'event';
|
|
||||||
|
|
||||||
$connectorObj = $this->connectors->getConnector($webhook->getType());
|
|
||||||
$connectorObj->dispatch($station, $webhook, $np, [Entity\StationWebhook::TRIGGER_ALL]);
|
|
||||||
|
|
||||||
$this->logger->popHandler();
|
|
||||||
|
|
||||||
return $handler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,16 +63,5 @@ final class LocalWebhookHandler
|
||||||
$staticNpPath,
|
$staticNpPath,
|
||||||
json_encode($np, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ?: ''
|
json_encode($np, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ?: ''
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send Nchan notification.
|
|
||||||
$this->logger->debug('Dispatching Nchan notification...');
|
|
||||||
|
|
||||||
$this->httpClient->post(
|
|
||||||
$this->environment->getInternalUri()
|
|
||||||
->withPath('/pub/' . urlencode($station->getShortName())),
|
|
||||||
[
|
|
||||||
'json' => $np,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user