conduit-ui/mattermost
Composer 安装命令:
composer require conduit-ui/mattermost
包简介
The Laravel way to build Mattermost bots — Saloon client, WebSocket, bot framework, streaming replies, slash commands, Filament panel
README 文档
README
The Laravel way to build Mattermost bots.
Status: Under active development. Not yet published on Packagist.
Contents
- Features
- Requirements
- Installation
- Configuration
- Quick start
- REST API client
- Sending messages
- Bot framework
- Slash commands
- Interactive messages
- Streaming replies
- Notifications
- Filament panel
- Artisan commands
- Testing
- Multi-server
- Local development
- License
Features
- Saloon-based REST client covering the full Mattermost API v4
- WebSocket client with auto-reconnect, typed events, and signal handling
- Event-driven bot framework — class-based handlers, middleware pipeline, queueable
- Permission guards — role, permission, channel-membership middleware
- Slash command routing
- Interactive messages — buttons, menus, action handlers
- Streaming replies — observer-driven create → edit → finalize flow
- Fluent message builder with attachments, threading, file uploads
- Laravel notification channel + broadcast driver
- Filament admin panel for status, message log, health
- Artisan commands —
mattermost:listen,mattermost:post,mattermost:health - First-class testing —
Mattermost::fake(), fixture helpers, request assertions - Multi-server support via named connections
Requirements
- PHP 8.4+
- Laravel 13+
Installation
composer require conduit-ui/mattermost php artisan vendor:publish --tag=mattermost-config
Set the minimum env in your .env:
MATTERMOST_URL=https://your-mattermost.example.com MATTERMOST_BOT_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx MATTERMOST_TEAM=your-team-name MATTERMOST_BOT_USER_ID=zzzzzzzzzzzzzzzzzzzzzzzzzz
Configuration
config/mattermost.php defines connections, slash commands, bot middleware, and websocket
settings. Most of it has sensible defaults — only connections typically needs touching.
return [ 'default' => env('MATTERMOST_CONNECTION', 'default'), 'connections' => [ 'default' => [ 'url' => env('MATTERMOST_URL', 'http://localhost:8065'), 'token' => env('MATTERMOST_BOT_TOKEN'), 'bot_user_id' => env('MATTERMOST_BOT_USER_ID'), 'team' => env('MATTERMOST_TEAM'), ], ], 'bot' => [ // Global middleware applied to every handler. Per-handler // middleware stacks on top via the #[Middleware(...)] attribute. 'middleware' => [ ConduitUI\Mattermost\Bot\Middleware\IgnoreBots::class, ConduitUI\Mattermost\Bot\Middleware\Dedup::class, ], 'dedup_ttl' => 60, 'rate_limit_seconds' => 30, 'allowed_channels' => [], ], ];
Quick start
use ConduitUI\Mattermost\Facades\Mattermost; // One-liner post Mattermost::posts()->createPost([ 'channel_id' => $channelId, 'message' => 'Hello from Laravel!', ]); // Or the fluent builder use ConduitUI\Mattermost\Messages\Message; Mattermost::posts()->createPost( Message::make('Deploy started') ->to($channelId) ->thread($rootPostId) ->toArray() );
REST API client
Every Mattermost API v4 endpoint is wrapped in a Saloon resource. Resources are auto-generated from the OpenAPI spec — every parameter and response is typed.
Mattermost::posts()->createPost([...]); Mattermost::posts()->updatePost($postId, ['message' => 'edited']); Mattermost::posts()->deletePost($postId); Mattermost::channels()->getChannelByName($teamId, 'town-square'); Mattermost::channels()->getChannelMembers($channelId); Mattermost::users()->getUserByUsername('alice'); Mattermost::users()->getUserStatus($userId); Mattermost::reactions()->saveReaction([ 'user_id' => $botUserId, 'post_id' => $postId, 'emoji_name' => 'eyes', ]); Mattermost::files()->uploadFile($channelId, $fileResource);
Available resources via the Mattermost facade: posts(), channels(), users(), teams(),
files(), reactions(), bots(), webhooks(), commands(), emoji(), status(), system().
Sending messages
The fluent Message builder produces a payload for POST /api/v4/posts without
making any HTTP call — pass the result to posts()->createPost().
use ConduitUI\Mattermost\Messages\Message; use ConduitUI\Mattermost\Messages\Attachment; $payload = Message::make('Build complete') ->to($channelId) ->thread($parentPostId) ->attachment( Attachment::make() ->color('#36a64f') ->title('main', 'https://github.com/...') ->text('All checks passed in 2m31s.') ->field('Branch', 'feat/foo', short: true) ->field('Commit', 'abc1234', short: true) ) ->toArray(); Mattermost::posts()->createPost($payload);
Bot framework
Mattermost WebSocket events route through a global + per-handler middleware pipeline to handler classes resolved from the container.
use App\Mattermost\HandleNewPost; use ConduitUI\Mattermost\Bot\Events\PostCreated; use ConduitUI\Mattermost\Facades\Mattermost; // Class-based handler Mattermost::on(PostCreated::class, HandleNewPost::class); // By Mattermost event name Mattermost::on('posted', HandleNewPost::class); // Wildcard — every event Mattermost::on('*', LogAllEvents::class);
Handler classes
use ConduitUI\Mattermost\Bot\Attributes\Middleware; use ConduitUI\Mattermost\Bot\Events\PostCreated; use ConduitUI\Mattermost\Bot\Handler; use ConduitUI\Mattermost\Bot\Middleware\IgnoreBots; use ConduitUI\Mattermost\Bot\Middleware\RateLimit; #[Middleware(IgnoreBots::class, RateLimit::class)] class HandleMentions extends Handler { public function handle(PostCreated $event): void { $this->reply($event, 'on it'); $this->react($event, 'eyes'); $this->typing($event); } }
reply(), react(), and typing() come from the base Handler class — they pick up the
correct connection, bot user id, and threading rules automatically.
Middleware
Built-in middleware ships under ConduitUI\Mattermost\Bot\Middleware:
| Middleware | What it does |
|---|---|
IgnoreBots |
Drops events authored by any configured bot user |
Dedup |
Cache-locks channel:post so duplicate event redeliveries are dropped |
RateLimit |
Per-channel cooldown between accepted posts. DMs exempt |
ChannelFilter |
Allowlist by channel id or name |
AdminOnly |
Requires the post author to be a system/team/channel admin |
Custom middleware implements the Middleware interface:
use ConduitUI\Mattermost\Bot\Events\Event; use ConduitUI\Mattermost\Bot\Middleware\Middleware; class StripPrefix implements Middleware { public function handle(Event $event, \Closure $next): mixed { if ($event instanceof PostCreated) { $event->message = ltrim($event->message, '!'); } return $next($event); } }
Guards
Permission-based middleware under ConduitUI\Mattermost\Bot\Middleware\Guards:
use ConduitUI\Mattermost\Bot\Attributes\Middleware; use ConduitUI\Mattermost\Bot\Middleware\Guards\ChannelMember; use ConduitUI\Mattermost\Bot\Middleware\Guards\RequiresPermission; use ConduitUI\Mattermost\Bot\Middleware\Guards\RequiresRole; #[Middleware(RequiresRole::class)] class DeployHandler extends Handler { public static array $roles = ['system_admin']; public function handle(PostCreated $event): void { /* ... */ } }
Queueable handlers
Handlers implementing ShouldQueue are dispatched onto Laravel's queue rather than
running inline:
class HandleHeavyJob extends Handler implements \Illuminate\Contracts\Queue\ShouldQueue { public function handle(PostCreated $event): void { /* ... */ } }
Configure the queue/connection per config/mattermost.php:
'bot' => [ 'queue' => 'mattermost', 'queue_connection' => 'redis', ],
Slash commands
Register slash commands with the same route-style API as event handlers:
use App\Mattermost\DeployHandler; use ConduitUI\Mattermost\Facades\Mattermost; use ConduitUI\Mattermost\SlashCommands\SlashCommand; use ConduitUI\Mattermost\SlashCommands\SlashCommandResponse; Mattermost::slash('deploy', DeployHandler::class); Mattermost::slash('status', fn (SlashCommand $cmd) => SlashCommandResponse::make()->inChannel('All systems go') );
Mattermost POSTs slash command webhooks to /mattermost/slash/{command}. The route is
auto-registered when enable_slash_commands is true in config.
Inside a handler:
class DeployHandler { public function __invoke(SlashCommand $cmd): SlashCommandResponse { return SlashCommandResponse::make() ->ephemeral("Deploying {$cmd->arg(0)}..."); } }
Interactive messages
Buttons and menus on Mattermost messages POST to /mattermost/interactive when clicked.
Register handlers for action ids:
use ConduitUI\Mattermost\Facades\Mattermost; use ConduitUI\Mattermost\Interactive\InteractiveAction; use ConduitUI\Mattermost\Interactive\InteractiveActionResponse; Mattermost::interactive('approve', fn (InteractiveAction $a) => InteractiveActionResponse::make()->update('Approved!') );
The button itself is built into your post's attachments:
Mattermost::posts()->createPost([ 'channel_id' => $channelId, 'message' => 'Ready to deploy?', 'props' => ['attachments' => [[ 'text' => 'Choose:', 'actions' => [ [ 'id' => 'deploy-approve', 'name' => 'Approve', 'type' => 'button', 'integration' => [ 'url' => url('/mattermost/interactive'), 'context' => ['action' => 'approve', 'env' => 'prod'], ], ], ], ]]], ]);
The context you set on the button is what the DTO surfaces as $action->context(),
and context.action is what actionId() returns.
Streaming replies
For bots replying with LLM tokens, the streaming layer creates a placeholder, edits it as chunks arrive, and finalizes:
use ConduitUI\Mattermost\Streaming\MattermostStreamingReply; $reply = MattermostStreamingReply::create($channelId, rootId: $event->postId); foreach ($llm->stream($prompt) as $chunk) { $reply->append($chunk); } $reply->finalize();
Behind the scenes: posts()->createPost() for the placeholder, batched updatePost()
calls debounced to respect rate limits, and a final patch with the full text.
Notifications
Use Mattermost as a Laravel notification channel:
use ConduitUI\Mattermost\Notifications\MattermostMessage; class DeployFinished extends Notification { public function via(mixed $notifiable): array { return ['mattermost']; } public function toMattermost(mixed $notifiable): MattermostMessage { return MattermostMessage::make("Deploy of `{$this->ref}` finished") ->color('#36a64f') ->field('Duration', '2m31s', short: true); } }
Route via routeNotificationFor('mattermost', ...) on the notifiable, or pass
['channel' => 'channel-id', 'connection' => 'staging'] to target a specific server.
A broadcast driver is also registered — set BROADCAST_CONNECTION=mattermost and
broadcast()->channel('mattermost:channel-id') posts to Mattermost.
Filament panel
Auto-discovered Filament pages give you a status dashboard, message log, and health check inside your existing Filament admin:
// In your PanelProvider use ConduitUI\Mattermost\Filament\MattermostPlugin; return $panel->plugin(MattermostPlugin::make());
Pages: Dashboard (connection + recent activity), Health (per-connection ping + latency), Message Log (recent posts).
Artisan commands
# Start the bot — connects WebSocket, dispatches events through the router php artisan mattermost:listen # One-off post from CLI php artisan mattermost:post town-square "Deploy underway" # Connectivity / auth check php artisan mattermost:health
mattermost:listen handles SIGTERM/SIGINT cleanly; running it under supervisord or systemd
is the recommended path.
Testing
Mattermost::fake() swaps the underlying client for an in-memory recorder. No HTTP, no
network — every API call is captured for assertions.
use ConduitUI\Mattermost\Facades\Mattermost; beforeEach(fn () => Mattermost::fake()); it('posts an announcement when deploy finishes', function () { DeployFinished::dispatch($build); Mattermost::assertPosted(fn ($payload) => $payload['channel_id'] === 'town-square' && str_contains($payload['message'], 'Deploy of') ); Mattermost::assertPostCount(1); });
Available assertions: assertSent, assertNotSent, assertNothingSent, assertPosted,
assertNotPosted, assertNothingPosted, assertPostCount, assertUpdated, assertPatched,
assertDeleted, assertReacted, assertNotReacted, assertFileUploaded,
preventStrayPosts, recorded.
Fixture helpers under MattermostFixtures build realistic payloads for tests:
use ConduitUI\Mattermost\Testing\MattermostFixtures; $post = MattermostFixtures::post(['message' => 'hello']); $wsEvent = MattermostFixtures::websocketEvent('posted', ['post' => $post]); $buttonClick = MattermostFixtures::fakeButtonClick('approve', context: ['env' => 'prod']);
Multi-server
Define multiple connections in config/mattermost.php:
'connections' => [ 'default' => [...], 'staging' => [ 'url' => env('MATTERMOST_STAGING_URL'), 'token' => env('MATTERMOST_STAGING_TOKEN'), ], ],
Pick the connection per call:
Mattermost::connection('staging')->posts()->createPost([...]);
Local development
A docker-compose.yml ships a local Mattermost for development:
docker compose up -d # start docker compose down # stop
After the first boot, create an admin via mmctl or the web UI at localhost:8065,
generate a bot token, and drop it into your .env.
The integration test suite runs against the live container:
vendor/bin/pest --testsuite=Integration
License
MIT
conduit-ui/mattermost 适用场景与选型建议
conduit-ui/mattermost 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 27 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「websocket」 「chat」 「bot」 「laravel」 「Mattermost」 「saloon」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 conduit-ui/mattermost 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 conduit-ui/mattermost 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 conduit-ui/mattermost 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Dialogflow php sdk
This simple PHP class allows you to easily generate Smartsupp.com JS chat code.
Client for the REST API plugin of the OpenFire Server
Laravel 5.6 Monolog custom telegram channel
The Yii2 extension module to chat registered users.
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 45
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-27