tourze/lark-custom-bot-bundle
Integrates Lark/Feishu custom bot webhooks into Symfony applications, providing configuration and services to send notifications and messages to group chats with minimal setup.
tourze/lark-custom-bot-bundle is a Tourze-ecosystem package for talking to a Lark/Feishu custom bot (the webhook you get from a group chat: Settings → Bots → Add Custom Bot). That means the day-to-day use case is narrow and concrete: push text, rich-post, or interactive-card messages into a chat from your Laravel app (deploy notices, order alerts, error escalations).
Important framing before you composer require: despite the Laravel context, this is a Symfony bundle, not a Laravel package. 0.0.1 ships with only a changelog link — there is no README, no documented config tree, no service provider, no facade. Treat it as source-code-first.
Minimal steps:
composer require tourze/lark-custom-bot-bundle:0.0.1 — pin the exact version. There is no stability promise at this tag.vendor/tourze/lark-custom-bot-bundle/src/ — the *Bundle class, any *Interface files, and the client/message classes.src/DependencyInjection/ — the extension class shows the config keys the bundle expects (webhook URL, secret, etc.). This is your de-facto documentation.Resources/config/services.yaml (or config/services.php in newer Tourze layouts) — the service IDs and constructor arguments you'll need to reproduce.Bundle, register the needed classes yourself in a service provider:// app/Providers/LarkBotServiceProvider.php
public function register(): void
{
$this->app->singleton(LarkBotClient::class, fn () => new LarkBotClient(
webhookUrl: config('services.lark_bot.webhook'),
secret: config('services.lark_bot.secret'),
));
}
If the above sounds like more glue than value, that is a legitimate read at 0.0.1 — see Gotchas.
Config over constructor. Keep the webhook URL and secret in config/services.php and .env (LARK_BOT_WEBHOOK, LARK_BOT_SECRET), then feed them into whichever class the bundle exposes. Do not let the Symfony config tree leak into your app — you're bypassing it anyway.
One bot per channel, resolved by name. Real apps need multiple bots (deploy channel, on-call channel, per-tenant). Model that as a small resolver rather than a hardcoded singleton:
class LarkBotManager
{
/** @param array<string, array{webhook: string, secret: ?string}> $bots */
public function __construct(private array $bots) {}
public function channel(string $name): LarkBotClient { /* cached factory */ }
}
Wrap it in a Notification channel. This is the highest-leverage integration for a Laravel dev: one LarkChannel class implementing send(), then Notification::route('lark', $webhook) or a routeNotificationForLark() on your User/Team model. It gives you queueing, ShouldQueue, and per-notification toLark() payload shaping for free — things the bundle will never provide in a Symfony-first design.
Always queue outbound sends. implements ShouldQueue on the notification/job, with tries = 3 and backoff = [1, 5, 15]. Custom bots are rate-limited (roughly 5 msg/sec and 100/min per bot); a burst of exception alerts will happily trip this.
Signing. If the bot has a secret enabled, the request needs timestamp (seconds) and sign = base64(hmac_sha256(key: secret, data: "{timestamp}\n{secret}")). The HMAC key is the secret and the payload is the concatenation — this is the single most common misimplementation. Verify what the bundle actually does here rather than assuming.
Rich cards for anything beyond text. Text messages are fine for a one-line deploy ping; for error alerts use an interactive card (colored header, fields, a "view logs" button). Build the card as a PHP array in a dedicated LarkCard value object so it's testable and reusable, rather than inlining 60 lines of nested arrays in a job.
Test without network. Bind a fake client in the container during tests and assert on the captured payloads. For integration-style tests, Http::fake() works if the bundle ends up going through Laravel/Symfony's HTTP client; if it constructs its own Guzzle client internally, you'll want to decorate or wrap it behind your own interface so testing stays possible.
Local/CI environments. Gate sending behind config('services.lark_bot.enabled') (false in local/testing). Nobody wants a bot spamming the team channel from a developer's failing test suite.
"tourze/lark-custom-bot-bundle": "0.0.1" exactly (no ^), and isolate it behind your own thin interface (LarkNotifier) so swapping to raw Http::post() later is a one-file change.symfony/dependency-injection, symfony/config, symfony/http-kernel and friends in your vendor/ for what is, functionally, one HTTP POST. Audit composer why symfony/http-kernel after install. For many teams the honest recommendation is: read the ~200 lines of signing/payload logic, reimplement it with Http in 20 lines, and skip the dependency.{"code": 0, "msg": "success"} on success and non-zero code with HTTP 200 on failure. Check $response['code'] === 0; log code + msg on anything else.timestamp/sign you sent when debugging, and confirm the server clock is sane.vendor:publish, no config/lark.php. Any config keys you find in DependencyInjection/ are Symfony config-tree keys and won't be read by Laravel's config() unless you bridge them explicitly.src/ before reaching for inheritance — decorating a client or *ProviderInterface is the intended Tourze pattern and keeps you upgrade-safe. If there are no interfaces, wrap rather than extend.https://github.com/tourze/lark-custom-bot-bundle/commits/0.0.1 is the only release artifact. Diff against the next tag before upgrading; at 0.x, assume breaking.How can I help you explore Laravel packages today?