1. Go to this page and download the library: Download siberfx/linkedin-autopost library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// app/Models/Article.php
class Article extends Model implements ShareableOnLinkedIn
{
use PostsToLinkedIn;
public function isLiveForLinkedIn(): bool
{
return $this->status === 'published';
}
public function toLinkedInPost(): LinkPost
{
return LinkPost::make(route('blog.show', $this), $this->title);
}
}
use App\Models\User;
use Illuminate\Support\Facades\Gate;
Gate::define('manage-linkedin-autopost', fn (User $user) => $user->is_admin);
// spatie/laravel-permission
Gate::define('manage-linkedin-autopost', fn (User $user) => $user->hasRole('marketing'));
// a fixed list
Gate::define('manage-linkedin-autopost', fn (User $user) => in_array($user->email, config('app.admins'), true));
use Siberfx\LinkedInAutopost\Facades\LinkedIn;
if (LinkedIn::isConnected()) {
$connection = LinkedIn::connection();
$connection->name; // "Ada Lovelace"
$connection->email; // "[email protected]"
$connection->status; // active | expired | revoked | unknown
$connection->expiresAt; // CarbonImmutable|null
$connection->daysLeft(); // 53
$connection->toArray(); // everything above as an array, never the token
}
use Illuminate\Support\Str;
use Siberfx\LinkedInAutopost\Concerns\PostsToLinkedIn;
use Siberfx\LinkedInAutopost\Contracts\ShareableOnLinkedIn;
use Siberfx\LinkedInAutopost\Posts\LinkPost;
class Article extends Model implements ShareableOnLinkedIn
{
use PostsToLinkedIn;
public function isLiveForLinkedIn(): bool
{
return $this->status === 'published';
}
public function toLinkedInPost(): LinkPost
{
return LinkPost::make(url: route('blog.show', $this), title: $this->title)
->description(Str::limit(strip_tags($this->body), 250))
->commentary("New on the blog: {title}\n\n{url}");
}
}
// An enum
public function isLiveForLinkedIn(): bool
{
return $this->status === ArticleStatus::Published;
}
// A boolean plus a publish date (pair with "Scheduled publishing" in section 7)
public function isLiveForLinkedIn(): bool
{
return $this->is_published && $this->published_at?->isPast();
}
// Only some records go to LinkedIn
public function isLiveForLinkedIn(): bool
{
return $this->status === 'published' && $this->share_on_linkedin;
}
// Product launch with a price line
return LinkPost::make(route('shop.products.show', $this), $this->name)
->description($this->tagline)
->commentary("Just launched: {title} — €{$this->price}\n\n{url}");
// Events with a date in the text
return LinkPost::make(route('events.show', $this), $this->title)
->description($this->venue)
->commentary("Join us on {$this->starts_at->isoFormat('D MMMM')}: {title}\n\nTickets: {url}");
// Translated text, per model locale
return LinkPost::make(route('blog.show', $this), $this->title)
->commentary(__('linkedin.new_article', locale: $this->locale)."\n\n{url}");
Article::create(['title' => 'Hello', 'status' => 'published']); // queued
$draft->update(['status' => 'published']); // queued (draft → published)
$published->update(['title' => 'Typo fixed']); // not queued: it was already live
Article::where('id', 5)->update(['status' => 'published']); // not queued: no model events
$article->saveQuietly(); // not queued: no model events
'autopost' => [
'enabled' => (bool) env('LINKEDIN_AUTOPOST', false),
'in_console' => false,
'queue_connection' => 'redis', // null = your default connection
'queue' => 'linkedin', // null = the default queue
],
'job' => [
'tries' => 3,
'backoff' => [60, 300], // seconds before the 2nd and 3rd attempt
],
use Siberfx\LinkedInAutopost\Facades\LinkedIn;
LinkedIn::share($article); // now; returns the LinkedInPost record (re-sharing is allowed)
LinkedIn::queue($article); // in the background
LinkedIn::wasPosted($article); // bool, same as $article->wasPostedToLinkedIn()
$article->wasPostedToLinkedIn(); // bool
$article->linkedInPosts; // history: post_urn, status, trigger, error, posted_at
use Siberfx\LinkedInAutopost\Exceptions\LinkedInRequestFailed;
use Siberfx\LinkedInAutopost\Exceptions\NotConnected;
use Siberfx\LinkedInAutopost\Exceptions\NotShareable;
use Siberfx\LinkedInAutopost\Facades\LinkedIn;
class ArticleLinkedInController
{
public function __invoke(Article $article)
{
Gate::authorize('manage-linkedin-autopost');
try {
$post = LinkedIn::share($article);
} catch (NotConnected) {
return back()->with('error', 'Connect LinkedIn first.');
} catch (NotShareable) {
return back()->with('error', 'Publish the article before sharing it.');
} catch (InvalidArgumentException $e) {
return back()->with('error', $e->getMessage()); // e.g. a relative URL
} catch (LinkedInRequestFailed $e) {
if ($e->isUnauthorized()) {
return back()->with('error', 'The LinkedIn token expired. Connect again.');
}
return back()->with('error', $e->getMessage()); // "LinkedIn rejected the request: …"
}
return back()->with('success', 'Shared on LinkedIn: '.$post->post_urn);
}
}
if (! LinkedIn::wasPosted($article)) {
LinkedIn::queue($article);
}
use Siberfx\LinkedInAutopost\Models\LinkedInPost;
$article->linkedInPosts()->latest('id')->first(); // last attempt
LinkedInPost::query()->for($article)->posted()->get(); // successful posts of one model
LinkedInPost::query() // every failure this week
->where('status', LinkedInPost::STATUS_FAILED)
->where('created_at', '>=', now()->subWeek())
->with('shareable')
->get();
// Or react inside one of your own Livewire components
use Livewire\Attributes\On;
#[On('linkedin-autopost-shared')]
public function refreshHistory(?string $urn = null): void
{
$this->history = $this->article->linkedInPosts()->latest('id')->get();
}
// A blog: posted when published.
class Article extends Model implements ShareableOnLinkedIn
{
use PostsToLinkedIn;
public function isLiveForLinkedIn(): bool
{
return $this->status === 'published';
}
public function toLinkedInPost(): LinkPost
{
return LinkPost::make(route('blog.show', $this), $this->title)
->description(Str::limit(strip_tags($this->body), 250));
}
}
// Job vacancies: posted when active and still open.
class JobVacancy extends Model implements ShareableOnLinkedIn
{
use PostsToLinkedIn;
protected $casts = ['is_active' => 'boolean', 'closing_date' => 'date'];
public function isLiveForLinkedIn(): bool
{
return $this->is_active && ($this->closing_date === null || $this->closing_date->endOfDay()->isFuture());
}
public function toLinkedInPost(): LinkPost
{
return LinkPost::make(route('careers.show', $this), "We're hiring: {$this->title}")
->description("{$this->location} · {$this->type}")
->commentary("We're hiring: {title}\n{$this->location} · {$this->type}\n\nApply: {url}");
}
}
// AppServiceProvider::boot()
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::enforceMorphMap([
'article' => Article::class,
'vacancy' => JobVacancy::class,
'user' => User::class, // enforceMorphMap needs every morphed model listed
]);
use Siberfx\LinkedInAutopost\Events\Connected;
use Siberfx\LinkedInAutopost\Events\Disconnected;
use Siberfx\LinkedInAutopost\Events\Shared;
use Siberfx\LinkedInAutopost\Events\TokenExpiringSoon;
// Keep the LinkedIn post link on the model itself
Event::listen(Shared::class, function (Shared $event) {
if ($event->shareable instanceof Article && $event->post->post_urn !== null) {
$event->shareable->updateQuietly([
'linkedin_url' => "https://www.linkedin.com/feed/update/{$event->post->post_urn}/",
]);
}
});
// Audit log
Event::listen(Connected::class, fn (Connected $e) =>
activity()->log("LinkedIn connected as {$e->connection->name}"));
Event::listen(Disconnected::class, fn (Disconnected $e) =>
activity()->log('LinkedIn disconnected'.($e->revoked ? '' : ' (revocation not confirmed)')));
// Email the admins before the token runs out
Event::listen(TokenExpiringSoon::class, fn (TokenExpiringSoon $e) =>
Mail::to(config('app.admin_email'))->send(new LinkedInTokenExpiring($e->daysLeft)));
namespace App\Notifications;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Siberfx\LinkedInAutopost\Contracts\NotificationChannel;
use Siberfx\LinkedInAutopost\Exceptions\NotificationFailed;
use Siberfx\LinkedInAutopost\Notifications\Message;
final class DiscordChannel implements NotificationChannel
{
public function __construct(private readonly array $config = []) {}
public function configured(): bool
{
return filled($this->config['webhook_url'] ?? null);
}
public function send(Message $message): void
{
$content = "**{$message->emoji()} {$message->headline}**\n".implode("\n", $message->lines);
try {
$response = Http::timeout(10)->post($this->config['webhook_url'], ['content' => $content]);
} catch (ConnectionException $e) {
// redacted() keeps the webhook URL out of logs and failed_jobs
throw NotificationFailed::redacted('Could not reach Discord: '.$e->getMessage(), $this->config['webhook_url']);
}
if ($response->failed()) {
throw new NotificationFailed('Discord refused the message: HTTP '.$response->status());
}
}
}