├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── Client.php
├── Collections
└── WebmentionsCollection.php
├── Components
└── WebmentionLinks.php
├── Facades
└── Webmentions.php
├── Models
├── Author.php
├── Entry.php
├── Like.php
├── Mention.php
├── Model.php
├── Reply.php
└── Repost.php
└── WebmentionsServiceProvider.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `laravel-webmentions` will be documented in this file
4 |
5 | ## v0.6.0 - 2024-06-25
6 |
7 | - add Laravel 11 support
8 | - drop PHP7.4 & PHP8.0
9 | - drop Laravel 8 & Laravel 9
10 |
11 | ## v0.5.0 - 2023-08-10
12 |
13 | - add Laravel 10 support
14 |
15 | ## v0.4.0 - 2022-09-22
16 |
17 | - add Laravel 9 support
18 |
19 | ## v0.3.0 - 2021-03-01
20 |
21 | - add `` Blade component
22 |
23 | ## v0.2.0 - 2021-03-01
24 |
25 | - add `\Astrotomic\Webmentions\Client::count($url)` method
26 |
27 | ## v0.1.0 - 2021-02-27
28 |
29 | - initial release
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Astrotomic
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Webmentions
2 |
3 | [](https://packagist.org/packages/astrotomic/laravel-webmentions)
4 | [](https://github.com/Astrotomic/laravel-webmentions/blob/master/LICENSE)
5 | [](https://plant.treeware.earth/Astrotomic/laravel-webmentions)
6 | [](https://www.larabelles.com/)
7 |
8 | [](https://github.com/Astrotomic/laravel-webmentions/actions?query=workflow%3Arun-tests)
9 | [](https://styleci.io/repos/322693045)
10 | [](https://packagist.org/packages/astrotomic/laravel-webmentions)
11 |
12 | A simple client to retrieve [webmentions](https://webmention.io) for your pages.
13 |
14 | ## Installation
15 |
16 | You can install the package via composer:
17 |
18 | ```bash
19 | composer require astrotomic/laravel-webmentions
20 | ```
21 |
22 | ## Configuration
23 |
24 | At firsts you will have to add your [webmention.io](https://webmention.io) API access token to the `services.php` config file.
25 |
26 | ```php
27 | return [
28 | // ...
29 | 'webmention' => [
30 | 'token' => env('WEBMENTION_TOKEN'),
31 | ],
32 | // ...
33 | ];
34 | ```
35 |
36 | ## Usage
37 |
38 | You can retrieve all webmentions for a given URL by calling the `get()` method on the packages client.
39 |
40 | ```php
41 | use Astrotomic\Webmentions\Facades\Webmentions;
42 |
43 | $records = Webmentions::get('https://gummibeer.dev/blog/2020/human-readable-intervals');
44 | ```
45 |
46 | If you omit the url as argument it will automatically use `\Illuminate\Http\Request::url()` as default.
47 | The return value will be an instance of `\Astrotomic\Webmentions\Collections\WebmentionsCollection` which provides you with predefined filter methods.
48 | You can also use the shorthand methods on the client to retrieve a collection of likes, mentions, replies or reposts.
49 |
50 | ```php
51 | use Astrotomic\Webmentions\Facades\Webmentions;
52 |
53 | $likes = Webmentions::likes('https://gummibeer.dev/blog/2020/human-readable-intervals');
54 | $mentions = Webmentions::mentions('https://gummibeer.dev/blog/2020/human-readable-intervals');
55 | $replies = Webmentions::replies('https://gummibeer.dev/blog/2020/human-readable-intervals');
56 | $reposts = Webmentions::reposts('https://gummibeer.dev/blog/2020/human-readable-intervals');
57 | ```
58 |
59 | All items will be a corresponding instance of `\Astrotomic\Webmentions\Models\Like`, `\Astrotomic\Webmentions\Models\Mention`, `\Astrotomic\Webmentions\Models\Reply` or `\Astrotomic\Webmentions\Models\Repost`.
60 |
61 | If you only need the count of items you can use the `Webmentions::count()` method.
62 |
63 | ```php
64 | use Astrotomic\Webmentions\Facades\Webmentions;
65 |
66 | $counts = Webmentions::count('https://gummibeer.dev/blog/2020/human-readable-intervals');
67 | [
68 | 'count' => 52,
69 | 'type' => [
70 | 'like' => 23,
71 | 'mention' => 8,
72 | 'reply' => 16,
73 | 'repost' => 5,
74 | ],
75 | ];
76 | ```
77 |
78 | ### Caching
79 |
80 | The client uses a poor man cache by default - so per runtime every domain is only requested once.
81 | If you want extended caching behavior you should wrap the calls in a `Cache::remember()` for example.
82 |
83 | ### Blade Component
84 |
85 | To receive webmentions for your page you have to add two `` tags to your head.
86 | This package provides a `` Blade component that makes it easier for you.
87 |
88 | ```html
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | ```
98 |
99 | ## Testing
100 |
101 | ```bash
102 | composer test
103 | ```
104 |
105 | ## Changelog
106 |
107 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
108 |
109 | ## Contributing
110 |
111 | Please see [CONTRIBUTING](https://github.com/Astrotomic/.github/blob/master/CONTRIBUTING.md) for details. You could also be interested in [CODE OF CONDUCT](https://github.com/Astrotomic/.github/blob/master/CODE_OF_CONDUCT.md).
112 |
113 | ### Security
114 |
115 | If you discover any security related issues, please check [SECURITY](https://github.com/Astrotomic/.github/blob/master/SECURITY.md) for steps to report it.
116 |
117 | ## Credits
118 |
119 | - [Tom Witkowski](https://github.com/Gummibeer)
120 | - [All Contributors](../../contributors)
121 |
122 | ## License
123 |
124 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
125 |
126 | ## Treeware
127 |
128 | You're free to use this package, but if it makes it to your production environment I would highly appreciate you buying the world a tree.
129 |
130 | It’s now common knowledge that one of the best tools to tackle the climate crisis and keep our temperatures from rising above 1.5C is to [plant trees](https://www.bbc.co.uk/news/science-environment-48870920). If you contribute to my forest you’ll be creating employment for local families and restoring wildlife habitats.
131 |
132 | You can buy trees at [offset.earth/treeware](https://plant.treeware.earth/Astrotomic/laravel-webmentions)
133 |
134 | Read more about Treeware at [treeware.earth](https://treeware.earth)
135 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astrotomic/laravel-webmentions",
3 | "description": "",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "astrotomic",
8 | "laravel-webmentions",
9 | "laravel",
10 | "webmentions",
11 | "webmention"
12 | ],
13 | "authors": [
14 | {
15 | "name": "Tom Witkowski",
16 | "email": "gummibeer@astrotomic.info",
17 | "homepage": "https://gummibeer.dev",
18 | "role": "Developer"
19 | }
20 | ],
21 | "homepage": "https://github.com/astrotomic/laravel-webmentions",
22 | "require": {
23 | "php": "^8.1",
24 | "ext-json": "*",
25 | "guzzlehttp/guzzle": "^7.0.1",
26 | "illuminate/collections": "^10.0 || ^11.0",
27 | "illuminate/http": "^10.0 || ^11.0",
28 | "illuminate/support": "^10.0 || ^11.0"
29 | },
30 | "require-dev": {
31 | "gajus/dindent": "^2.0",
32 | "orchestra/testbench": "^8.0 || ^9.0",
33 | "phpunit/phpunit": "^9.3 || ^10.0"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "Astrotomic\\Webmentions\\": "src"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "Astrotomic\\Webmentions\\Tests\\": "tests"
43 | }
44 | },
45 | "config": {
46 | "sort-packages": true
47 | },
48 | "extra": {
49 | "laravel": {
50 | "providers": [
51 | "Astrotomic\\Webmentions\\WebmentionsServiceProvider"
52 | ]
53 | }
54 | },
55 | "scripts": {
56 | "test": "vendor/bin/phpunit"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | byDomain($domain)
26 | ->filter(fn (Entry $entry): bool => $this->extractPath($entry->target) === $this->extractPath($url))
27 | ->values();
28 | }
29 |
30 | public function likes(?string $url = null): Collection
31 | {
32 | return $this->get($url)->likes();
33 | }
34 |
35 | public function mentions(?string $url = null): Collection
36 | {
37 | return $this->get($url)->mentions();
38 | }
39 |
40 | public function replies(?string $url = null): Collection
41 | {
42 | return $this->get($url)->replies();
43 | }
44 |
45 | public function reposts(?string $url = null): Collection
46 | {
47 | return $this->get($url)->reposts();
48 | }
49 |
50 | /**
51 | * @param string|null $url
52 | * @return array
53 | *
54 | * @see https://webmention.io/api/count?target={$url}
55 | */
56 | public function count(?string $url = null): array
57 | {
58 | $items = $this->get($url);
59 |
60 | return [
61 | 'count' => $items->count(),
62 | 'type' => [
63 | 'like' => $items->likes()->count(),
64 | 'mention' => $items->mentions()->count(),
65 | 'reply' => $items->replies()->count(),
66 | 'repost' => $items->reposts()->count(),
67 | ],
68 | ];
69 | }
70 |
71 | protected function byDomain(string $domain): WebmentionsCollection
72 | {
73 | if (! isset(static::$webmentions[$domain])) {
74 | $webmentions = new WebmentionsCollection();
75 |
76 | $page = 0;
77 | do {
78 | $entries = Http::get(self::BASE_URL, [
79 | 'token' => config('services.webmention.token'),
80 | 'domain' => $domain,
81 | 'per-page' => self::PER_PAGE,
82 | 'page' => $page,
83 | ])->json()['children'] ?? [];
84 |
85 | $webmentions->push(...$entries);
86 |
87 | $page++;
88 | } while (count($entries) >= self::PER_PAGE);
89 |
90 | static::$webmentions[$domain] = $webmentions
91 | ->map(fn (array $entry): ?Entry => Entry::make($entry))
92 | ->filter()
93 | ->values();
94 | }
95 |
96 | return static::$webmentions[$domain];
97 | }
98 |
99 | protected function extractPath(string $url): ?string
100 | {
101 | return trim(parse_url($url, PHP_URL_PATH), '/');
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Collections/WebmentionsCollection.php:
--------------------------------------------------------------------------------
1 | filter(fn (Entry $entry): bool => $entry instanceof Like)->toBase();
20 | }
21 |
22 | /**
23 | * @return \Illuminate\Support\Collection|\Astrotomic\Webmentions\Models\Mention[]
24 | */
25 | public function mentions(): Collection
26 | {
27 | return $this->filter(fn (Entry $entry): bool => $entry instanceof Mention)->toBase();
28 | }
29 |
30 | /**
31 | * @return \Illuminate\Support\Collection|\Astrotomic\Webmentions\Models\Reply[]
32 | */
33 | public function replies(): Collection
34 | {
35 | return $this->filter(fn (Entry $entry): bool => $entry instanceof Reply)->toBase();
36 | }
37 |
38 | /**
39 | * @return \Illuminate\Support\Collection|\Astrotomic\Webmentions\Models\Mention[]
40 | */
41 | public function reposts(): Collection
42 | {
43 | return $this->filter(fn (Entry $entry): bool => $entry instanceof Repost)->toBase();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Components/WebmentionLinks.php:
--------------------------------------------------------------------------------
1 | domain = $domain ?? parse_url(Request::url(), PHP_URL_HOST);
15 | }
16 |
17 | public function render(): string
18 | {
19 | return <<
21 |
22 | HTML;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Facades/Webmentions.php:
--------------------------------------------------------------------------------
1 | $author['name'],
15 | 'avatar' => $author['photo'] ?: null,
16 | 'url' => $author['url'] ?: null,
17 | ]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Models/Entry.php:
--------------------------------------------------------------------------------
1 | $entry['wm-id'],
41 | 'url' => $entry['url'],
42 | 'source' => $entry['wm-source'],
43 | 'target' => $entry['wm-target'],
44 | 'published_at' => $entry['published']
45 | ? Carbon::parse($entry['published'])
46 | : null,
47 | 'created_at' => $entry['published']
48 | ? Carbon::parse($entry['published'])
49 | : Carbon::parse($entry['wm-received']),
50 | 'author' => Author::fromWebmention($entry['author']),
51 | 'text' => $entry['content']['text'] ?? null,
52 | 'html' => isset($entry['content']['html'])
53 | ? new HtmlString($entry['content']['html'])
54 | : null,
55 | 'raw' => $entry,
56 | ]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Models/Like.php:
--------------------------------------------------------------------------------
1 | $value) {
10 | $this->{$field} = $value;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/Reply.php:
--------------------------------------------------------------------------------
1 | app->singleton(Client::class);
14 | }
15 |
16 | public function boot(): void
17 | {
18 | $this->callAfterResolving(BladeCompiler::class, function (BladeCompiler $blade) {
19 | $blade->component(WebmentionLinks::class, 'webmention-links');
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------