Source path: app/Services/Publishers/LinkedInPublisher.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Services\Publishers;

use App\Models\Media;
use App\Models\PostTarget;
use App\Models\SocialAccount;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

class LinkedInPublisher extends BasePublisher
{
    private const API = 'https://api.linkedin.com/rest';

    private function li(string $tok): PendingRequest
    {
        return Http::withToken($tok)->withHeaders([
            'LinkedIn-Version' => config('social.linkedin.api_version'),
            'X-Restli-Protocol-Version' => '2.0.0',
        ]);
    }

    public function discoverAccounts(string $t): array
    {
        $u = Http::withToken($t)->get('https://api.linkedin.com/v2/userinfo')->throw()->json();
        return [['id' => 'urn:li:person:' . $u['sub'], 'name' => $u['name'] ?? 'LinkedIn member']];
        // Company pages: needs the Community Management API; list them via organizationAcls and add as extra accounts.
    }

    public function refreshToken(SocialAccount $a): void
    {
        if (!$a->refresh_token) throw new \RuntimeException('LinkedIn token expired; reconnect required.');
        $c = config('social.linkedin');
        $r = Http::asForm()->post($c['token_url'], [
            'grant_type' => 'refresh_token', 'refresh_token' => $a->refresh_token,
            'client_id' => $c['client_id'], 'client_secret' => $c['client_secret'],
        ])->throw();
        $this->saveTokens($a, $r->json());
    }

    public function publish(PostTarget $t): array
    {
        $a = $t->socialAccount;
        $tok = $this->token($a);
        $media = $this->media($t)->first();

        $body = [
            'author' => $a->external_id,
            'commentary' => $t->post->caption,
            'visibility' => 'PUBLIC',
            'distribution' => ['feedDistribution' => 'MAIN_FEED', 'targetEntities' => [], 'thirdPartyDistributionChannels' => []],
            'lifecycleState' => 'PUBLISHED',
            'isReshareDisabledByAuthor' => false,
        ];

        if ($media) {
            $urn = $media->type === 'video' ? $this->uploadVideo($tok, $a->external_id, $media)
                                            : $this->uploadImage($tok, $a->external_id, $media);
            $body['content'] = ['media' => ['id' => $urn]];
        }

        $res = $this->li($tok)->post(self::API . '/posts', $body)->throw();
        $urn = $res->header('x-restli-id');
        return ['id' => $urn, 'url' => "https://www.linkedin.com/feed/update/$urn"];
    }

    private function uploadImage(string $tok, string $owner, Media $m): string
    {
        $init = $this->li($tok)->post(self::API . '/images?action=initializeUpload', [
            'initializeUploadRequest' => ['owner' => $owner],
        ])->throw()->json('value');

        $f = $this->tmp($m);
        try {
            Http::withToken($tok)->withBody(file_get_contents($f), 'application/octet-stream')->put($init['uploadUrl'])->throw();
        } finally { @unlink($f); }
        return $init['image'];
    }

    private function uploadVideo(string $tok, string $owner, Media $m): string
    {
        $f = $this->tmp($m);
        try {
            $size = filesize($f);
            $init = $this->li($tok)->post(self::API . '/videos?action=initializeUpload', [
                'initializeUploadRequest' => ['owner' => $owner, 'fileSizeBytes' => $size, 'uploadCaptions' => false, 'uploadThumbnail' => false],
            ])->throw()->json('value');

            $h = fopen($f, 'r'); $etags = [];
            foreach ($init['uploadInstructions'] as $part) {
                fseek($h, $part['firstByte']);
                $chunk = fread($h, $part['lastByte'] - $part['firstByte'] + 1);
                $r = Http::withToken($tok)->timeout(300)->withBody($chunk, 'application/octet-stream')->put($part['uploadUrl'])->throw();
                $etags[] = $r->header('ETag');
            }
            fclose($h);

            $this->li($tok)->post(self::API . '/videos?action=finalizeUpload', [
                'finalizeUploadRequest' => ['video' => $init['video'], 'uploadToken' => $init['uploadToken'] ?? '', 'uploadedPartIds' => $etags],
            ])->throw();
            return $init['video'];
        } finally { @unlink($f); }
    }
}
TikTok
