Showing
8 changed files
with
434 additions
and
1 deletions
| ... | @@ -113,6 +113,88 @@ DaitoString::convertToUtf8('/tmp/source.csv'); | ... | @@ -113,6 +113,88 @@ DaitoString::convertToUtf8('/tmp/source.csv'); |
| 113 | DaitoString::convertToUtf8('/tmp/source.csv', '/tmp/out', 'source_utf8.csv', 1); | 113 | DaitoString::convertToUtf8('/tmp/source.csv', '/tmp/out', 'source_utf8.csv', 1); |
| 114 | ``` | 114 | ``` |
| 115 | 115 | ||
| 116 | +## DaitoGoogleChat (Laravel webhook utility) | ||
| 117 | + | ||
| 118 | +### 1) Publish config | ||
| 119 | +```bash | ||
| 120 | +php artisan vendor:publish --tag=daito-google-chat-config | ||
| 121 | +``` | ||
| 122 | + | ||
| 123 | +This creates `config/daito-google-chat.php`. | ||
| 124 | + | ||
| 125 | +### 2) Minimal `.env` setup | ||
| 126 | +```dotenv | ||
| 127 | +DAITO_GOOGLE_CHAT_ENABLED=true | ||
| 128 | +DAITO_GOOGLE_CHAT_WEBHOOK_URL=https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=... | ||
| 129 | +``` | ||
| 130 | + | ||
| 131 | +### 3) Send immediately | ||
| 132 | +```php | ||
| 133 | +use Daito\Lib\DaitoGoogleChat; | ||
| 134 | + | ||
| 135 | +DaitoGoogleChat::sendText('Deploy success', array( | ||
| 136 | + 'service' => 'billing-api', | ||
| 137 | + 'env' => 'production', | ||
| 138 | +)); | ||
| 139 | +``` | ||
| 140 | + | ||
| 141 | +### 4) Send via queue | ||
| 142 | +```php | ||
| 143 | +use Daito\Lib\DaitoGoogleChat; | ||
| 144 | + | ||
| 145 | +DaitoGoogleChat::queueText('Large import finished', array( | ||
| 146 | + 'job_id' => 12345, | ||
| 147 | + 'duration' => '02:31', | ||
| 148 | +)); | ||
| 149 | +``` | ||
| 150 | + | ||
| 151 | +### 5) Advanced payload | ||
| 152 | +```php | ||
| 153 | +DaitoGoogleChat::sendPayload(array( | ||
| 154 | + 'text' => 'Custom payload from daito/lib', | ||
| 155 | +)); | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +### 6) cardV2 (send immediately) | ||
| 159 | +```php | ||
| 160 | +use Daito\Lib\DaitoGoogleChat; | ||
| 161 | + | ||
| 162 | +$arrCardV2 = array( | ||
| 163 | + 'cardId' => 'deploy-status', | ||
| 164 | + 'card' => array( | ||
| 165 | + 'header' => array( | ||
| 166 | + 'title' => 'Deploy Success', | ||
| 167 | + 'subtitle' => 'billing-api / production', | ||
| 168 | + ), | ||
| 169 | + 'sections' => array( | ||
| 170 | + array( | ||
| 171 | + 'widgets' => array( | ||
| 172 | + array( | ||
| 173 | + 'decoratedText' => array( | ||
| 174 | + 'topLabel' => 'Version', | ||
| 175 | + 'text' => 'v2.3.1', | ||
| 176 | + ), | ||
| 177 | + ), | ||
| 178 | + array( | ||
| 179 | + 'decoratedText' => array( | ||
| 180 | + 'topLabel' => 'Duration', | ||
| 181 | + 'text' => '01:45', | ||
| 182 | + ), | ||
| 183 | + ), | ||
| 184 | + ), | ||
| 185 | + ), | ||
| 186 | + ), | ||
| 187 | + ), | ||
| 188 | +); | ||
| 189 | + | ||
| 190 | +DaitoGoogleChat::sendCardV2($arrCardV2); | ||
| 191 | +``` | ||
| 192 | + | ||
| 193 | +### 7) cardV2 (send via queue) | ||
| 194 | +```php | ||
| 195 | +DaitoGoogleChat::queueCardV2($arrCardV2); | ||
| 196 | +``` | ||
| 197 | + | ||
| 116 | ## QueryLog (Laravel shared package) | 198 | ## QueryLog (Laravel shared package) |
| 117 | 199 | ||
| 118 | > **Provider registration note** | 200 | > **Provider registration note** | ... | ... |
| ... | @@ -20,7 +20,8 @@ | ... | @@ -20,7 +20,8 @@ |
| 20 | "extra": { | 20 | "extra": { |
| 21 | "laravel": { | 21 | "laravel": { |
| 22 | "providers": [ | 22 | "providers": [ |
| 23 | - "Daito\\Lib\\DaitoQueryLog\\Providers\\DaitoQueryLogProvider" | 23 | + "Daito\\Lib\\DaitoQueryLog\\Providers\\DaitoQueryLogProvider", |
| 24 | + "Daito\\Lib\\DaitoGoogleChat\\Providers\\DaitoGoogleChatProvider" | ||
| 24 | ] | 25 | ] |
| 25 | } | 26 | } |
| 26 | } | 27 | } | ... | ... |
src/DaitoGoogleChat.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib; | ||
| 4 | + | ||
| 5 | +use Daito\Lib\DaitoGoogleChat\DaitoGoogleChatManager; | ||
| 6 | +use RuntimeException; | ||
| 7 | + | ||
| 8 | +class DaitoGoogleChat | ||
| 9 | +{ | ||
| 10 | + public static function sendText($text, array $arrContext = array(), $webhookUrl = null): array | ||
| 11 | + { | ||
| 12 | + return self::manager()->sendText($text, $arrContext, $webhookUrl); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + public static function sendPayload(array $arrPayload, $webhookUrl = null): array | ||
| 16 | + { | ||
| 17 | + return self::manager()->sendPayload($arrPayload, $webhookUrl); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public static function sendCardV2(array $arrCardV2, $webhookUrl = null): array | ||
| 21 | + { | ||
| 22 | + return self::manager()->sendCardV2($arrCardV2, $webhookUrl); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public static function sendCardsV2(array $arrCardsV2, $webhookUrl = null): array | ||
| 26 | + { | ||
| 27 | + return self::manager()->sendCardsV2($arrCardsV2, $webhookUrl); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static function queueText($text, array $arrContext = array(), $webhookUrl = null): void | ||
| 31 | + { | ||
| 32 | + self::manager()->queueText($text, $arrContext, $webhookUrl); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public static function queuePayload(array $arrPayload, $webhookUrl = null): void | ||
| 36 | + { | ||
| 37 | + self::manager()->queuePayload($arrPayload, $webhookUrl); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public static function queueCardV2(array $arrCardV2, $webhookUrl = null): void | ||
| 41 | + { | ||
| 42 | + self::manager()->queueCardV2($arrCardV2, $webhookUrl); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public static function queueCardsV2(array $arrCardsV2, $webhookUrl = null): void | ||
| 46 | + { | ||
| 47 | + self::manager()->queueCardsV2($arrCardsV2, $webhookUrl); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + private static function manager(): DaitoGoogleChatManager | ||
| 51 | + { | ||
| 52 | + if (!function_exists('app')) { | ||
| 53 | + throw new RuntimeException('Laravel app container is required for DaitoGoogleChat.'); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + $manager = app(DaitoGoogleChatManager::class); | ||
| 57 | + if (!$manager instanceof DaitoGoogleChatManager) { | ||
| 58 | + throw new RuntimeException('Can not resolve DaitoGoogleChatManager from container.'); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + return $manager; | ||
| 62 | + } | ||
| 63 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\DaitoGoogleChat; | ||
| 4 | + | ||
| 5 | +use Daito\Lib\DaitoGoogleChat\Jobs\DaitoGoogleChatWebhookJob; | ||
| 6 | +use Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient; | ||
| 7 | + | ||
| 8 | +class DaitoGoogleChatManager | ||
| 9 | +{ | ||
| 10 | + /** | ||
| 11 | + * @var \Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient | ||
| 12 | + */ | ||
| 13 | + private $client; | ||
| 14 | + | ||
| 15 | + public function __construct(DaitoGoogleChatWebhookClient $client) | ||
| 16 | + { | ||
| 17 | + $this->client = $client; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public function sendText($text, array $arrContext = array(), $webhookUrl = null): array | ||
| 21 | + { | ||
| 22 | + return $this->sendPayload( | ||
| 23 | + array( | ||
| 24 | + 'text' => $this->buildTextContent((string) $text, $arrContext), | ||
| 25 | + ), | ||
| 26 | + $webhookUrl | ||
| 27 | + ); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public function sendPayload(array $arrPayload, $webhookUrl = null): array | ||
| 31 | + { | ||
| 32 | + return $this->client->send($arrPayload, $webhookUrl); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public function sendCardV2(array $arrCardV2, $webhookUrl = null): array | ||
| 36 | + { | ||
| 37 | + return $this->sendCardsV2(array($arrCardV2), $webhookUrl); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public function sendCardsV2(array $arrCardsV2, $webhookUrl = null): array | ||
| 41 | + { | ||
| 42 | + return $this->sendPayload( | ||
| 43 | + array( | ||
| 44 | + 'cardsV2' => array_values($arrCardsV2), | ||
| 45 | + ), | ||
| 46 | + $webhookUrl | ||
| 47 | + ); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + public function queueText($text, array $arrContext = array(), $webhookUrl = null): void | ||
| 51 | + { | ||
| 52 | + $this->queuePayload( | ||
| 53 | + array( | ||
| 54 | + 'text' => $this->buildTextContent((string) $text, $arrContext), | ||
| 55 | + ), | ||
| 56 | + $webhookUrl | ||
| 57 | + ); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + public function queuePayload(array $arrPayload, $webhookUrl = null): void | ||
| 61 | + { | ||
| 62 | + $job = new DaitoGoogleChatWebhookJob($arrPayload, $webhookUrl); | ||
| 63 | + if (config('daito-google-chat.queue_connection') !== null) { | ||
| 64 | + $job->onConnection(config('daito-google-chat.queue_connection')); | ||
| 65 | + } | ||
| 66 | + if (config('daito-google-chat.queue_name') !== null) { | ||
| 67 | + $job->onQueue(config('daito-google-chat.queue_name')); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + dispatch($job); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public function queueCardV2(array $arrCardV2, $webhookUrl = null): void | ||
| 74 | + { | ||
| 75 | + $this->queueCardsV2(array($arrCardV2), $webhookUrl); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public function queueCardsV2(array $arrCardsV2, $webhookUrl = null): void | ||
| 79 | + { | ||
| 80 | + $this->queuePayload( | ||
| 81 | + array( | ||
| 82 | + 'cardsV2' => array_values($arrCardsV2), | ||
| 83 | + ), | ||
| 84 | + $webhookUrl | ||
| 85 | + ); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + private function buildTextContent(string $text, array $arrContext = array()): string | ||
| 89 | + { | ||
| 90 | + $textContent = trim($text); | ||
| 91 | + if ($arrContext === array()) { | ||
| 92 | + return $textContent; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + $arrContextLines = array(); | ||
| 96 | + foreach ($arrContext as $key => $value) { | ||
| 97 | + $arrContextLines[] = sprintf('%s: %s', (string) $key, $this->normalizeContextValue($value)); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + return $textContent . "\n" . implode("\n", $arrContextLines); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + private function normalizeContextValue($value): string | ||
| 104 | + { | ||
| 105 | + if (is_scalar($value) || $value === null) { | ||
| 106 | + return (string) $value; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + $json = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); | ||
| 110 | + return $json !== false ? $json : '[unserializable]'; | ||
| 111 | + } | ||
| 112 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\DaitoGoogleChat\Jobs; | ||
| 4 | + | ||
| 5 | +use Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient; | ||
| 6 | +use Illuminate\Bus\Queueable; | ||
| 7 | +use Illuminate\Contracts\Queue\ShouldQueue; | ||
| 8 | +use Illuminate\Foundation\Bus\Dispatchable; | ||
| 9 | +use Illuminate\Queue\InteractsWithQueue; | ||
| 10 | +use Illuminate\Queue\SerializesModels; | ||
| 11 | + | ||
| 12 | +class DaitoGoogleChatWebhookJob implements ShouldQueue | ||
| 13 | +{ | ||
| 14 | + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||
| 15 | + | ||
| 16 | + protected $arrPayload; | ||
| 17 | + protected $webhookUrl; | ||
| 18 | + | ||
| 19 | + public $tries; | ||
| 20 | + public $backoff; | ||
| 21 | + | ||
| 22 | + public function __construct(array $arrPayload, $webhookUrl = null) | ||
| 23 | + { | ||
| 24 | + $this->arrPayload = $arrPayload; | ||
| 25 | + $this->webhookUrl = $webhookUrl !== null ? (string) $webhookUrl : null; | ||
| 26 | + $this->tries = max(1, (int) config('daito-google-chat.queue_tries', 3)); | ||
| 27 | + $this->backoff = max(1, (int) config('daito-google-chat.queue_backoff_seconds', 10)); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public function handle(DaitoGoogleChatWebhookClient $client): void | ||
| 31 | + { | ||
| 32 | + $client->send($this->arrPayload, $this->webhookUrl); | ||
| 33 | + } | ||
| 34 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\DaitoGoogleChat\Providers; | ||
| 4 | + | ||
| 5 | +use Daito\Lib\DaitoGoogleChat\DaitoGoogleChatManager; | ||
| 6 | +use Daito\Lib\DaitoGoogleChat\Services\DaitoGoogleChatWebhookClient; | ||
| 7 | +use Illuminate\Support\ServiceProvider; | ||
| 8 | + | ||
| 9 | +class DaitoGoogleChatProvider extends ServiceProvider | ||
| 10 | +{ | ||
| 11 | + public function register(): void | ||
| 12 | + { | ||
| 13 | + $this->mergeConfigFrom( | ||
| 14 | + __DIR__ . '/../config/daito-google-chat.php', | ||
| 15 | + 'daito-google-chat' | ||
| 16 | + ); | ||
| 17 | + | ||
| 18 | + $this->app->singleton(DaitoGoogleChatWebhookClient::class, function () { | ||
| 19 | + return new DaitoGoogleChatWebhookClient(); | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + $this->app->singleton(DaitoGoogleChatManager::class, function ($app) { | ||
| 23 | + return new DaitoGoogleChatManager( | ||
| 24 | + $app->make(DaitoGoogleChatWebhookClient::class) | ||
| 25 | + ); | ||
| 26 | + }); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public function boot(): void | ||
| 30 | + { | ||
| 31 | + $this->publishes( | ||
| 32 | + array( | ||
| 33 | + __DIR__ . '/../config/daito-google-chat.php' => config_path('daito-google-chat.php'), | ||
| 34 | + ), | ||
| 35 | + 'daito-google-chat-config' | ||
| 36 | + ); | ||
| 37 | + } | ||
| 38 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Daito\Lib\DaitoGoogleChat\Services; | ||
| 4 | + | ||
| 5 | +use Illuminate\Support\Facades\Http; | ||
| 6 | +use Illuminate\Support\Facades\Log; | ||
| 7 | +use RuntimeException; | ||
| 8 | + | ||
| 9 | +class DaitoGoogleChatWebhookClient | ||
| 10 | +{ | ||
| 11 | + public function send(array $arrPayload, $webhookUrl = null): array | ||
| 12 | + { | ||
| 13 | + if (!(bool) config('daito-google-chat.enabled', false)) { | ||
| 14 | + return array( | ||
| 15 | + 'success' => 0, | ||
| 16 | + 'status' => 'disabled', | ||
| 17 | + 'message' => 'Daito Google Chat notification is disabled.', | ||
| 18 | + ); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + $resolvedWebhookUrl = $this->resolveWebhookUrl($webhookUrl); | ||
| 22 | + $this->assertWebhookUrlIsAllowed($resolvedWebhookUrl); | ||
| 23 | + | ||
| 24 | + $response = Http::acceptJson() | ||
| 25 | + ->asJson() | ||
| 26 | + ->timeout((int) config('daito-google-chat.timeout_seconds', 5)) | ||
| 27 | + ->connectTimeout((int) config('daito-google-chat.connect_timeout_seconds', 3)) | ||
| 28 | + ->retry( | ||
| 29 | + (int) config('daito-google-chat.retry_times', 1), | ||
| 30 | + (int) config('daito-google-chat.retry_sleep_ms', 200) | ||
| 31 | + ) | ||
| 32 | + ->post($resolvedWebhookUrl, $arrPayload); | ||
| 33 | + | ||
| 34 | + if ($response->successful()) { | ||
| 35 | + return array( | ||
| 36 | + 'success' => 1, | ||
| 37 | + 'status' => 'sent', | ||
| 38 | + 'http_code' => $response->status(), | ||
| 39 | + ); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + $arrError = array( | ||
| 43 | + 'success' => 0, | ||
| 44 | + 'status' => 'failed', | ||
| 45 | + 'http_code' => $response->status(), | ||
| 46 | + 'message' => trim((string) $response->body()), | ||
| 47 | + ); | ||
| 48 | + | ||
| 49 | + Log::warning('DaitoGoogleChat send failed.', $arrError); | ||
| 50 | + | ||
| 51 | + if ((bool) config('daito-google-chat.throw_on_error', false)) { | ||
| 52 | + throw new RuntimeException('DaitoGoogleChat send failed: HTTP ' . (string) $response->status()); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + return $arrError; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + private function resolveWebhookUrl($webhookUrl): string | ||
| 59 | + { | ||
| 60 | + $resolvedWebhookUrl = trim((string) ($webhookUrl ?: config('daito-google-chat.default_webhook_url', ''))); | ||
| 61 | + if ($resolvedWebhookUrl === '') { | ||
| 62 | + throw new RuntimeException('Google Chat webhook URL is empty.'); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + return $resolvedWebhookUrl; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + private function assertWebhookUrlIsAllowed(string $webhookUrl): void | ||
| 69 | + { | ||
| 70 | + if (!(bool) config('daito-google-chat.validate_webhook_host', true)) { | ||
| 71 | + return; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + $host = strtolower((string) parse_url($webhookUrl, PHP_URL_HOST)); | ||
| 75 | + if ($host === '') { | ||
| 76 | + throw new RuntimeException('Google Chat webhook URL is invalid.'); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + $arrAllowedWebhookHosts = array_map('strtolower', (array) config('daito-google-chat.allowed_webhook_hosts', array())); | ||
| 80 | + if (!in_array($host, $arrAllowedWebhookHosts, true)) { | ||
| 81 | + throw new RuntimeException('Google Chat webhook host is not allowed: ' . $host); | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +return array( | ||
| 4 | + 'enabled' => env('DAITO_GOOGLE_CHAT_ENABLED', false), | ||
| 5 | + 'default_webhook_url' => env('DAITO_GOOGLE_CHAT_WEBHOOK_URL', ''), | ||
| 6 | + 'validate_webhook_host' => env('DAITO_GOOGLE_CHAT_VALIDATE_WEBHOOK_HOST', true), | ||
| 7 | + 'allowed_webhook_hosts' => array( | ||
| 8 | + 'chat.googleapis.com', | ||
| 9 | + ), | ||
| 10 | + 'timeout_seconds' => env('DAITO_GOOGLE_CHAT_TIMEOUT', 5), | ||
| 11 | + 'connect_timeout_seconds' => env('DAITO_GOOGLE_CHAT_CONNECT_TIMEOUT', 3), | ||
| 12 | + 'retry_times' => env('DAITO_GOOGLE_CHAT_RETRY_TIMES', 1), | ||
| 13 | + 'retry_sleep_ms' => env('DAITO_GOOGLE_CHAT_RETRY_SLEEP_MS', 200), | ||
| 14 | + 'queue_connection' => env('DAITO_GOOGLE_CHAT_QUEUE_CONNECTION', null), | ||
| 15 | + 'queue_name' => env('DAITO_GOOGLE_CHAT_QUEUE_NAME', null), | ||
| 16 | + 'queue_tries' => env('DAITO_GOOGLE_CHAT_QUEUE_TRIES', 3), | ||
| 17 | + 'queue_backoff_seconds' => env('DAITO_GOOGLE_CHAT_QUEUE_BACKOFF', 10), | ||
| 18 | + 'throw_on_error' => env('DAITO_GOOGLE_CHAT_THROW_ON_ERROR', false), | ||
| 19 | +); |
-
Please register or sign in to post a comment