baseUrl = rtrim($baseUrl, '/'); $this->cookieFile = sys_get_temp_dir() . '/pleroma_admin_cookies_' . session_id() . '.txt'; } public function login($username, $password) { // Step 1: Initialise session by visiting admin page $adminPage = $this->baseUrl . '/pleroma/admin/'; $ch = curl_init($adminPage); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => $this->userAgent, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_COOKIEJAR => $this->cookieFile, CURLOPT_COOKIEFILE => $this->cookieFile, CURLOPT_TIMEOUT => 30 ]); curl_exec($ch); curl_close($ch); // Step 2: OAuth password grant (exact flow used by modern AdminFE) $oauthUrl = $this->baseUrl . '/oauth/token'; $postData = [ 'grant_type' => 'password', 'username' => $username, 'password' => $password, // These are the default AdminFE client credentials – work on nearly all instances 'client_id' => 'ECfTrzsW11bkG7simInOJf614Ee2sBhOlvE-69KDMzs', 'client_secret' => 'r5EbNudkfePUwvRmc7DbafP0yOXpUC4lvipSrU8QOpo', ]; $ch = curl_init($oauthUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($postData), CURLOPT_USERAGENT => $this->userAgent, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_COOKIEJAR => $this->cookieFile, CURLOPT_COOKIEFILE => $this->cookieFile, CURLOPT_HTTPHEADER => [ 'Content-Type: application/x-www-form-urlencoded', 'Accept: application/json' ], CURLOPT_TIMEOUT => 30 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($response, true); if ($httpCode === 200 && isset($data['access_token'])) { $_SESSION['bearer_token'] = $data['access_token']; return ['success' => true, 'message' => 'Admin OAuth login successful', 'token' => $data['access_token']]; } return ['success' => false, 'message' => 'Login failed', 'http_code' => $httpCode, 'response' => $response]; } public function getCookieFile() { return $this->cookieFile; } public function cleanup() { if (file_exists($this->cookieFile)) { unlink($this->cookieFile); } } } class PleromaEmojiUploader { private $baseUrl; private $bearerToken; private $maxFileSize = 1048576; // 1 MB private $maxDimensions = 256; // 256×256 px public function __construct($baseUrl, $bearerToken) { $this->baseUrl = rtrim($baseUrl, '/'); $this->bearerToken = $bearerToken; } private function validateFile($file, &$error) { $error = ''; if (!is_uploaded_file($file['tmp_name'])) { $error = 'No file uploaded'; return false; } if ($file['size'] > $this->maxFileSize) { $error = 'File too large (max 1 MB)'; return false; } $filename = pathinfo($file['name'], PATHINFO_FILENAME); if (!preg_match('/^[a-zA-Z0-9_]+$/', $filename)) { $error = 'Shortcode (filename without extension) may only contain letters, numbers and underscores'; return false; } $allowed = ['png', 'gif', 'webp', 'jpg', 'jpeg']; $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($ext, $allowed)) { $error = 'Only PNG, GIF, WebP, JPG/JPEG allowed'; return false; } $info = getimagesize($file['tmp_name']); if (!$info || $info[0] > $this->maxDimensions || $info[1] > $this->maxDimensions) { $error = 'Image dimensions too large (max 256×256 px)'; return false; } return true; } public function uploadEmoji($packName, $uploadedFile) { $error = ''; if (!$this->validateFile($uploadedFile, $error)) { return ['success' => false, 'error' => $error]; } // Modern endpoint (2024–2026 Akkoma/Pleroma) $url = $this->baseUrl . '/api/v1/pleroma/emoji/packs/files?name=' . urlencode($packName); $postData = [ 'file' => new CURLFile($uploadedFile['tmp_name'], $uploadedFile['type'], $uploadedFile['name']) ]; $ch = curl_init($url); $headers = [ 'Authorization: Bearer ' . $this->bearerToken, 'Accept: application/json' ]; curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postData, CURLOPT_USERAGENT => 'Mozilla/5.0', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 60 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $shortcode = pathinfo($uploadedFile['name'], PATHINFO_FILENAME); return [ 'success' => ($httpCode >= 200 && $httpCode < 300), 'http_code' => $httpCode, 'shortcode' => $shortcode, 'pack' => $packName, 'response' => $response ]; } } ?>
You are now authenticated.
'; if (!empty($result['token'])) { echo 'Switch to the Emoji Uploader tab.
'; echo '' . htmlspecialchars($result['message']) . '
'; if (isset($result['http_code'])) echo 'HTTP Code: ' . $result['http_code'] . '
'; echo 'Shortcode: :' . htmlspecialchars($uploadResult['shortcode']) . ':
'; echo 'Pack: ' . htmlspecialchars($uploadResult['pack']) . '
'; echo 'You may need to refresh the admin panel or reload emojis for it to appear immediately.
'; } else { echo 'HTTP Code: ' . $uploadResult['http_code'] . '
'; if (!empty($uploadResult['response'])) { echo '' . htmlspecialchars($uploadResult['response']) . ''; } } echo '