├── src ├── Concerns │ ├── Accounts.php │ ├── Apps.php │ ├── Statuses.php │ └── Streaming.php ├── Traits │ └── Mastodon.php ├── Providers │ └── MastodonServiceProvider.php ├── Facades │ └── Mastodon.php ├── Contracts │ └── Factory.php └── MastodonClient.php ├── docs ├── macro.md └── trait.md ├── streaming_example.php ├── LICENSE ├── composer.json └── README.md /src/Concerns/Accounts.php: -------------------------------------------------------------------------------- 1 | get('/accounts/verify_credentials'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/macro.md: -------------------------------------------------------------------------------- 1 | # Macroable 2 | 3 | Extend any method by your self. 4 | 5 | ## Register in AppServiceProvider.php 6 | 7 | ```php 8 | use Revolution\Mastodon\Facades\Mastodon; 9 | 10 | public function boot() 11 | { 12 | Mastodon::macro('instance', function () { 13 | return $this->get('/instance'); 14 | }); 15 | } 16 | ``` 17 | 18 | ## Use somewhere 19 | ```php 20 | $instance = Mastodon::domain($domain)->instance(); 21 | ``` 22 | -------------------------------------------------------------------------------- /src/Concerns/Apps.php: -------------------------------------------------------------------------------- 1 | post('/apps', $params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Traits/Mastodon.php: -------------------------------------------------------------------------------- 1 | make(Factory::class) 14 | ->domain($this->mastodonDomain()) 15 | ->token($this->mastodonToken()); 16 | } 17 | 18 | abstract protected function mastodonDomain(): string; 19 | 20 | abstract protected function mastodonToken(): string; 21 | } 22 | -------------------------------------------------------------------------------- /streaming_example.php: -------------------------------------------------------------------------------- 1 | token($token) 14 | ->streaming($url, function (string $event, string $data) { 15 | // event: update|notification|delete 16 | // data: JSON 17 | 18 | if ($event === 'update') { 19 | $status = json_decode($data, true); 20 | 21 | echo strip_tags($status['account']['acct']).PHP_EOL; 22 | echo strip_tags($status['content']).PHP_EOL.PHP_EOL; 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/Providers/MastodonServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->scoped(Factory::class, MastodonClient::class); 18 | } 19 | 20 | /** 21 | * Get the services provided by the provider. 22 | */ 23 | public function provides(): array 24 | { 25 | return [ 26 | Factory::class, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Concerns/Statuses.php: -------------------------------------------------------------------------------- 1 | $limit, 16 | 'since_id' => $since_id, 17 | ]; 18 | 19 | return $this->get($url, $query); 20 | } 21 | 22 | /** 23 | * Create new status. 24 | */ 25 | public function createStatus(string $status, ?array $options = null): array 26 | { 27 | $url = '/statuses'; 28 | 29 | $params = array_merge(['status' => $status], $options ?? []); 30 | 31 | return $this->post($url, $params); 32 | } 33 | 34 | /** 35 | * Retrieve status. 36 | */ 37 | public function status(int $status_id): array 38 | { 39 | $url = "/statuses/$status_id"; 40 | 41 | return $this->get($url); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 kawax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revolution/laravel-mastodon-api", 3 | "description": "Mastodon API for Laravel", 4 | "keywords": [ 5 | "mastodon", 6 | "laravel" 7 | ], 8 | "license": "MIT", 9 | "require": { 10 | "php": "^8.2", 11 | "ext-json": "*", 12 | "illuminate/http": "^11.0||^12.0", 13 | "revolution/laravel-notification-mastodon": "^3.5", 14 | "revolution/socialite-mastodon": "^1.5" 15 | }, 16 | "require-dev": { 17 | "orchestra/testbench": "^10.0", 18 | "laravel/pint": "^1.22" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Revolution\\Mastodon\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "Tests\\": "tests/" 28 | } 29 | }, 30 | "authors": [ 31 | { 32 | "name": "kawax", 33 | "email": "kawaxbiz@gmail.com" 34 | } 35 | ], 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "Revolution\\Mastodon\\Providers\\MastodonServiceProvider" 40 | ], 41 | "aliases": { 42 | "Mastodon": "Revolution\\Mastodon\\Facades\\Mastodon" 43 | } 44 | } 45 | }, 46 | "minimum-stability": "stable" 47 | } 48 | -------------------------------------------------------------------------------- /src/Facades/Mastodon.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'Authorization' => 'Bearer '.$this->token, 22 | ], 23 | 'stream' => true, 24 | ]; 25 | 26 | $response = $this->client->request('GET', $url, $options); 27 | 28 | $body = $response->getBody(); 29 | 30 | $stream = new CachingStream($body); 31 | 32 | while (! $stream->eof()) { 33 | $line = Utils::readLine($stream); 34 | $line = trim($line); 35 | 36 | if (Str::startsWith($line, 'event: ')) { 37 | $event = substr($line, 7); 38 | 39 | $data = substr(trim(Utils::readLine($stream)), 6); 40 | 41 | $callback($event, $data); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/trait.md: -------------------------------------------------------------------------------- 1 | # Mastodon Trait 2 | Like a Laravel Notifications. 3 | 4 | Add `Mastodon` trait to User model.(or other model) 5 | 6 | ```php 7 | domain; 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | protected function mastodonToken() 33 | { 34 | return $this->token; 35 | } 36 | } 37 | ``` 38 | 39 | Add `mastodonDomain()` and `mastodonToken()`(abstract). 40 | 41 | Trait has `mastodon()` that returns `Mastodon` instance. 42 | 43 | ```php 44 | public function __invoke(Request $request) 45 | { 46 | $statuses = $request->user() 47 | ->mastodon() 48 | ->statuses($account_id); 49 | 50 | dd($statuses); 51 | } 52 | ``` 53 | 54 | ## Already mastodon() exists 55 | 56 | ```php 57 | use Mastodon { 58 | Mastodon::mastodon as toots; 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | apiEndpoint()) 37 | ->when(isset($this->client), fn (PendingRequest $client) => $client->setClient($this->client)) 38 | ->when(filled($this->token), fn (PendingRequest $client) => $client->withToken($this->token)) 39 | ->send($method, $api, $options); 40 | 41 | $this->response = $response->toPsrResponse(); 42 | 43 | return $response->json() ?? []; 44 | } 45 | 46 | public function get(string $api, array $query = []): array 47 | { 48 | $options = []; 49 | 50 | if (! empty($query)) { 51 | $options['query'] = $query; 52 | } 53 | 54 | return $this->call('GET', $api, $options); 55 | } 56 | 57 | public function post(string $api, array $params = []): array 58 | { 59 | $options = []; 60 | 61 | if (! empty($params)) { 62 | $options['form_params'] = $params; 63 | } 64 | 65 | return $this->call('POST', $api, $options); 66 | } 67 | 68 | public function apiEndpoint(): string 69 | { 70 | return $this->domain.$this->api_base.$this->api_version; 71 | } 72 | 73 | public function setClient(ClientInterface $client): static 74 | { 75 | $this->client = $client; 76 | 77 | return $this; 78 | } 79 | 80 | public function domain(string $domain): static 81 | { 82 | $this->domain = trim($domain, '/'); 83 | 84 | return $this; 85 | } 86 | 87 | public function token(#[\SensitiveParameter] string $token): static 88 | { 89 | $this->token = $token; 90 | 91 | return $this; 92 | } 93 | 94 | public function apiVersion(string $api_version): static 95 | { 96 | $this->api_version = $api_version; 97 | 98 | return $this; 99 | } 100 | 101 | public function apiBase(string $api_base): static 102 | { 103 | $this->api_base = $api_base; 104 | 105 | return $this; 106 | } 107 | 108 | public function getResponse(): ?ResponseInterface 109 | { 110 | return $this->response; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mastodon API for Laravel 2 | 3 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/invokable/laravel-mastodon-api) 4 | 5 | ## Requirements 6 | - PHP >= 8.2 7 | - Laravel >= 11.0 8 | 9 | ## Installation 10 | 11 | ### Composer 12 | ``` 13 | composer require revolution/laravel-mastodon-api 14 | ``` 15 | 16 | [Socialite](https://github.com/invokable/socialite-mastodon) and [Notification](https://github.com/invokable/laravel-notification-mastodon) packages will also be installed. 17 | 18 | ## Usage 19 | 20 | ### Registering an application 21 | 22 | #### By Web UI 23 | 1. Go to your Mastodon's user preferences page. 24 | 2. Go to development page. 25 | 26 | #### By API 27 | ```php 28 | use Revolution\Mastodon\Facades\Mastodon; 29 | 30 | class MastodonController 31 | { 32 | public function app() 33 | { 34 | $client_name = 'my-app'; 35 | $redirect_uris = 'https://my-instance/callback'; 36 | $scopes = 'read write follow'; 37 | 38 | $app_info = Mastodon::domain('https://example.com') 39 | ->createApp($client_name, $redirect_uris, $scopes); 40 | 41 | dd($app_info); 42 | //[ 43 | // 'id' => '', 44 | // 'client_id' => '', 45 | // 'client_secret' => '', 46 | //] 47 | } 48 | } 49 | ``` 50 | 51 | ### OAuth authentication 52 | Use https://github.com/invokable/socialite-mastodon 53 | 54 | Save account info.(`id`, `token`, `username`, `acct`...and more.) 55 | 56 | ### Get statuses 57 | ```php 58 | use Revolution\Mastodon\Facades\Mastodon; 59 | 60 | $statuses = Mastodon::domain('https://example.com') 61 | ->token('token') 62 | ->statuses($account_id); 63 | 64 | dd($statuses); 65 | ``` 66 | 67 | ### Get one status 68 | ```php 69 | use Revolution\Mastodon\Facades\Mastodon; 70 | 71 | $status = Mastodon::domain('https://example.com') 72 | ->token('token') 73 | ->status($status_id); 74 | 75 | dd($status); 76 | ``` 77 | 78 | ### Post status 79 | ```php 80 | use Revolution\Mastodon\Facades\Mastodon; 81 | 82 | Mastodon::domain('https://example.com')->token('token'); 83 | $response = Mastodon::createStatus('test1'); 84 | $response = Mastodon::createStatus('test2', ['visibility' => 'unlisted']); 85 | 86 | dd($response); 87 | ``` 88 | 89 | ### Any API by `get` or `post` method 90 | ```php 91 | use Revolution\Mastodon\Facades\Mastodon; 92 | 93 | $response = Mastodon::domain('https://example.com') 94 | ->token('token') 95 | ->get('/timelines/public', ['local' => true]); 96 | ``` 97 | 98 | ```php 99 | use Revolution\Mastodon\Facades\Mastodon; 100 | 101 | $response = Mastodon::domain('https://example.com') 102 | ->token('token') 103 | ->post('/follows', ['uri' => '']); 104 | ``` 105 | 106 | ### Any API can call by `call` method 107 | ```php 108 | use Revolution\Mastodon\Facades\Mastodon; 109 | 110 | $response = Mastodon::domain('https://example.com') 111 | ->token('token') 112 | ->call('DELETE', '/statuses/1'); 113 | ``` 114 | 115 | ### Other methods 116 | Check public methods in `Contracts/Factory.php` 117 | 118 | ## Streaming API 119 | Edit `$token` and `$url` in streaming_example.php 120 | 121 | ``` 122 | php ./streaming_example.php 123 | ``` 124 | 125 | `Ctrl+C` to quit. 126 | 127 | ## LICENSE 128 | MIT 129 | --------------------------------------------------------------------------------